Compare commits
509 Commits
android-0.
...
android-cl
Author | SHA1 | Date | |
---|---|---|---|
05c2dbd388 | |||
c8e1643326 | |||
d72c936a0e | |||
06d4d7d10d | |||
b506b5e740 | |||
2d65bd373c | |||
7c869adf58 | |||
61a7566007 | |||
9d42901079 | |||
fb31818a3c | |||
6355214b5f | |||
d5c0704477 | |||
411131b8a6 | |||
10ed266d2c | |||
bccfe03b5d | |||
a44ac8a45c | |||
5610752c6d | |||
7047913b45 | |||
a41aa79920 | |||
4fcc1121b7 | |||
514aa51224 | |||
0c46dc9bd0 | |||
4b7f951e32 | |||
a58a9d7540 | |||
d3eaebd324 | |||
37c366a528 | |||
8f6289984b | |||
7629bb54ce | |||
ee7d227990 | |||
4cc940c995 | |||
2336eebdd0 | |||
62035050c5 | |||
6775d57c22 | |||
d3a1910b2e | |||
aa43d960dc | |||
2e3047274e | |||
a3cef11e08 | |||
543fb51d76 | |||
4328db1908 | |||
69fbb5dc92 | |||
0c5d8f8e9e | |||
b88e150803 | |||
35fe44fc59 | |||
464adb9e71 | |||
66d370abeb | |||
11aded07ca | |||
5d0861e22e | |||
5778eb9d1c | |||
0e47bc5042 | |||
8f9a6922ad | |||
05cc0634b7 | |||
583666695c | |||
e67ba59e51 | |||
ab619f904d | |||
f2f7418c8b | |||
23c55d50fb | |||
e0acb322a5 | |||
2a1427054d | |||
d878d2d8a4 | |||
5386829edf | |||
5d74e7ffef | |||
332ec1e0ad | |||
060262ee52 | |||
c75fe55e56 | |||
bccf5e0965 | |||
6bd905a027 | |||
fc0b393b14 | |||
f2acde73fe | |||
77a7f5f603 | |||
d235da093f | |||
795d3ab314 | |||
dd40931a23 | |||
8b71e4fc2e | |||
ed61f0414e | |||
06ef95c7ac | |||
2936bfc2b7 | |||
9c655ffebf | |||
9d8fb684d2 | |||
0d744e269c | |||
36ffb6eda4 | |||
df7ee4bd05 | |||
d98d6abff3 | |||
260cc8a5a2 | |||
a0a1df8093 | |||
e4110eb894 | |||
d0264bf475 | |||
7ec8b0a592 | |||
e3ecac8fec | |||
4fdc7940dd | |||
9920ad34cd | |||
8a7025038a | |||
a47c80df8c | |||
a1a5aeaf6c | |||
3a8eeabe3e | |||
3e34bac295 | |||
66d0dce40c | |||
c8d3ee7aac | |||
959537adc2 | |||
7ca050fdf5 | |||
07130abf23 | |||
ba82d59b89 | |||
8819dc5f30 | |||
a034b78dfd | |||
2dc56d57d4 | |||
3aff1c4f75 | |||
55509adda6 | |||
19def413c1 | |||
63a0e2117f | |||
21e0b2a667 | |||
6ce15e27de | |||
7a0a56373d | |||
37da05ca98 | |||
f003bbbfa4 | |||
a6978bb161 | |||
b1ec76de5a | |||
dc58796c97 | |||
c7075c3fc4 | |||
8d4f1b174d | |||
6f29991829 | |||
a414b10ce8 | |||
030fc60445 | |||
9d215353e8 | |||
f0a2166ae0 | |||
af097474de | |||
5a1fc32da4 | |||
7218b79643 | |||
ccd5ae45df | |||
3584890277 | |||
4e17010f59 | |||
aa8009cb70 | |||
30d1816c43 | |||
02c25ba174 | |||
8f8bbcb19f | |||
696ae2bfc0 | |||
0ea468ea71 | |||
a2d9adb071 | |||
b8cc64d4ea | |||
6e3e99c62f | |||
84a63cc911 | |||
b8c3b7e3df | |||
04e190d2d0 | |||
ce2a762db2 | |||
76014f2081 | |||
6d570646f1 | |||
104c17cb9c | |||
0860ee83b0 | |||
707c0e9aa8 | |||
163bc2ce58 | |||
cec1f8fe52 | |||
97f52f8139 | |||
0094cc5637 | |||
01c994e7b2 | |||
b60ae00fd7 | |||
2340500083 | |||
a91261f5ca | |||
a145729353 | |||
0270444a94 | |||
01e22f4fb5 | |||
2e892841cb | |||
e271dc90ae | |||
77ced0bd1f | |||
ac67533ef2 | |||
305c834aa1 | |||
cbb2973b36 | |||
f360ab4d5d | |||
296d21d1d0 | |||
a5dd751227 | |||
d47bdf85d2 | |||
c2a33541b3 | |||
3e46d98481 | |||
124b1499b4 | |||
2d8529e691 | |||
4a135023b9 | |||
e9fbe8c2ef | |||
228d0204fc | |||
0ebecd9b64 | |||
c062a0f803 | |||
4f24517de9 | |||
d8636ff563 | |||
4e57f78931 | |||
ccbbc3c368 | |||
b9374b5ead | |||
ceaff935d6 | |||
255702b6bb | |||
2e68aa900b | |||
67eddba621 | |||
29fb1f0689 | |||
de605a1d5b | |||
41b49b7bc8 | |||
c691a11c3d | |||
2f6b1189ae | |||
a7a328238b | |||
c9c31ccb76 | |||
8457f279f2 | |||
fbf353858e | |||
99ef07d1fc | |||
a5f5e97e37 | |||
165bcc1c9d | |||
45efd6670f | |||
b601722b31 | |||
14e1a2dca1 | |||
d2385166cb | |||
ed17d59896 | |||
55cfd455ca | |||
f207cf3116 | |||
5825d1d2a5 | |||
077f9902a9 | |||
d77cbde3b3 | |||
62ff63665f | |||
1377aceb18 | |||
c7617ba856 | |||
08c6018483 | |||
f9f283409d | |||
4ef42cb462 | |||
65428dda8e | |||
6311ab4b67 | |||
0c20a45207 | |||
6712148010 | |||
07e6e293bc | |||
2dd1655e1e | |||
97037fe1d8 | |||
d79f797558 | |||
42649e02ea | |||
c6aeb79944 | |||
fb66ec62d6 | |||
15f1e18da1 | |||
15caf8a97c | |||
44989a42f2 | |||
3867eb6fda | |||
0755e79b1a | |||
3a1e43b322 | |||
6a2d494921 | |||
be14d65899 | |||
c42dc725d6 | |||
83ab1d09ae | |||
af30dc8e24 | |||
0268ce13ad | |||
5b897bc993 | |||
1fbf6b1b72 | |||
8095eed241 | |||
8916c123ef | |||
b742dd8ee8 | |||
b02b446e46 | |||
01b07fed5b | |||
2fa205daec | |||
3644d738ee | |||
c044c4de4d | |||
c705527113 | |||
8b79af434e | |||
2c8e9d62be | |||
9832779a50 | |||
9f535a3260 | |||
0beaec366f | |||
8c288ad559 | |||
df5d5ad38e | |||
8b1648c37b | |||
cea42e9ec4 | |||
64c44838a8 | |||
76f9259ee7 | |||
d437f45132 | |||
fc618ad9e5 | |||
15275680e8 | |||
7848a81110 | |||
207f9837d0 | |||
cda09ea4f5 | |||
2a15994a76 | |||
e954953130 | |||
8b51c26a6b | |||
d40f806be6 | |||
bf5b29da76 | |||
18c4276ba0 | |||
a4107e974a | |||
8dd35f6c0f | |||
5a4be4ae86 | |||
9527725760 | |||
b6a8fc02f5 | |||
7794a7db5b | |||
c03debf332 | |||
fbc56d4eb9 | |||
2a050b3ca1 | |||
48c8d84d2f | |||
b6d6258e95 | |||
47215495ed | |||
9e45f1998b | |||
788445f6ce | |||
a057e4a512 | |||
7a09670097 | |||
c92d881a51 | |||
8c2ba03880 | |||
9c9f871667 | |||
7f9758197d | |||
f6d1c093e4 | |||
5e045bc23b | |||
f667a81c6d | |||
a13a405b49 | |||
383ece497f | |||
075c7c09d7 | |||
7cec48e55f | |||
7ddd3c69c8 | |||
9d965a5504 | |||
abd4a99654 | |||
92c734624d | |||
81d0441d2b | |||
9c7b2142cf | |||
3da41888b7 | |||
bf47b901b7 | |||
54a446ebdb | |||
0eb0c67616 | |||
409d823dec | |||
0208e58a3b | |||
6d18e50a3a | |||
c748610280 | |||
7768c624f9 | |||
72ed6bd170 | |||
3d5b9938fd | |||
03b5927447 | |||
b7a6b4acd5 | |||
e27e1e55bd | |||
0b0511dbce | |||
02c370a04a | |||
4810c9e990 | |||
cc801de79d | |||
93fd4f7e0c | |||
abd9908a21 | |||
52d278134c | |||
c60e4f6b3e | |||
5b7452ff90 | |||
014fc9b79b | |||
9ca0ce3192 | |||
99d720c685 | |||
c46ba4f24b | |||
522178598b | |||
5b6658531e | |||
333455b738 | |||
5ebdeedb2b | |||
0af8d2145f | |||
5ac6d51289 | |||
238ab91092 | |||
0af8ed90f7 | |||
c761287a8a | |||
a77674603a | |||
9a2382d886 | |||
0d58d81bce | |||
1e4300cb83 | |||
28b0950990 | |||
536102658a | |||
6754f6b5b1 | |||
06d1903184 | |||
4d13e8adfd | |||
145b249394 | |||
a00c08bb49 | |||
449627be3d | |||
27be4aacb2 | |||
f4f849182d | |||
32d8a7112c | |||
4dbfff292b | |||
228d27d82b | |||
9d0858ad17 | |||
480dacb7f2 | |||
84edc743f5 | |||
b7dfc45b1e | |||
bd0ebc8852 | |||
8c0e2228a5 | |||
4dafc3e5af | |||
291294435a | |||
99588c3cb1 | |||
f7904e0c7e | |||
ae2fa4dce7 | |||
bdbc777a52 | |||
320e8d5153 | |||
c53b98d2b9 | |||
e1236d2824 | |||
d7ac916eeb | |||
5b9203f77d | |||
9757d6e396 | |||
c2ff90af91 | |||
c1e8719d0e | |||
56198bf771 | |||
73286f43f6 | |||
cd3157038c | |||
9359c7a726 | |||
058f41ec73 | |||
e4c9095626 | |||
dd90ea9874 | |||
53c7770e4e | |||
21274f0335 | |||
4ccf3e713d | |||
2ef4d71cdb | |||
c2bc999847 | |||
d3f37a21a5 | |||
658d2a68e2 | |||
490148cb5b | |||
430d56b681 | |||
405bb3317e | |||
bbb41c9c54 | |||
5c9c438e28 | |||
27239cf09d | |||
b853c3af39 | |||
48f84f1a1b | |||
eb4ae2c66d | |||
17be8fb3f7 | |||
bf5accb121 | |||
4749e470b5 | |||
0553815777 | |||
57d81fb14d | |||
ee97af6e4f | |||
6397a93cac | |||
03465185f9 | |||
ddd9a195e6 | |||
664985461a | |||
6d340dc056 | |||
f312b7c6f1 | |||
a42bd73de3 | |||
4424392bdc | |||
bfe71213f8 | |||
cd2a12c8ed | |||
4ca2ed756f | |||
a4abfd8fb8 | |||
9d754c29ae | |||
0101e63bce | |||
75fe8bfbe0 | |||
806f6edbf1 | |||
767ff4f3d2 | |||
07fafb03b6 | |||
e98f86b29a | |||
18bc4f141d | |||
a3792bad7a | |||
745bd3fa94 | |||
97692a4635 | |||
3fe7575dab | |||
a7fc5090f3 | |||
6154f64120 | |||
0e5ca23732 | |||
c47e7dab31 | |||
472fa6d49e | |||
60ece9d4d2 | |||
afd656c6b4 | |||
78e250a207 | |||
134db2ecd3 | |||
53caad9f2a | |||
742df967e2 | |||
5d0c5c30eb | |||
cb9924a0bf | |||
3ceed9a6b3 | |||
c3d95a608d | |||
1b004a628e | |||
b080bd387a | |||
7a429674a7 | |||
45a1511cab | |||
6c7be97ed5 | |||
3244509cab | |||
43a20d18c5 | |||
e2332543ec | |||
4d68da45b4 | |||
084ed85467 | |||
1d060fd419 | |||
a95ca82a3e | |||
c13101d535 | |||
7dc7697c14 | |||
91fb0a2248 | |||
2663cc7d57 | |||
f9fce317d3 | |||
f87a3eb03c | |||
36a0f2c678 | |||
f8fe3f082a | |||
6e130185de | |||
006fc1dc51 | |||
87836ddab6 | |||
ff7154b525 | |||
7a829236b9 | |||
d3edd31155 | |||
455726f05a | |||
5dc9d729f4 | |||
6d42b93de4 | |||
39b54c41ab | |||
f8920298ee | |||
cc3b37c4c0 | |||
0cdd5a5d88 | |||
b61bc8f5ef | |||
2a6a2ef7a7 | |||
245f69f2d3 | |||
4c43b6f5d5 | |||
28230c914d | |||
f7e1acdb68 | |||
995cd7f327 | |||
1e024c22da | |||
4e853a753f | |||
01547e9a20 | |||
09b2af14fd | |||
f5d06470e4 | |||
abe20c95d0 | |||
c9abfa80f0 | |||
f34ef46dbf | |||
0c30296888 | |||
3fd373ad8c | |||
41091dce25 | |||
0fbaeb0ea4 | |||
19791a5965 | |||
9d4c0aa839 | |||
0b12a952cb | |||
a95aa6e89b | |||
f13c772509 | |||
0f28c4d807 | |||
a649d434c7 | |||
23c5710d42 | |||
52a60b37e2 | |||
adb5c56f26 | |||
177ac2e3a1 | |||
1219725e6a |
23
.mtn-ignore
23
.mtn-ignore
@ -22,3 +22,26 @@ _jsp\.java$
|
|||||||
~$
|
~$
|
||||||
/build/
|
/build/
|
||||||
/classes/
|
/classes/
|
||||||
|
|
||||||
|
# Android-specific ignores
|
||||||
|
^routerjars/libs
|
||||||
|
local.properties
|
||||||
|
signing.properties
|
||||||
|
|
||||||
|
#IntelliJ IDEA
|
||||||
|
^.idea
|
||||||
|
.*.iml
|
||||||
|
.*.ipr
|
||||||
|
.*.iws
|
||||||
|
|
||||||
|
#Gradle
|
||||||
|
^.gradle
|
||||||
|
build
|
||||||
|
|
||||||
|
# I2P-specific ignores
|
||||||
|
^res/raw/blocklist_txt
|
||||||
|
^res/raw/certificates_zip
|
||||||
|
^res/raw/hosts_txt
|
||||||
|
^res/raw/license_
|
||||||
|
^scripts/build.number
|
||||||
|
^scripts/version.properties
|
||||||
|
18
.tx/config
Normal file
18
.tx/config
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, zh_CN: zh
|
||||||
|
|
||||||
|
[I2P.android]
|
||||||
|
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||||
|
source_file = app/src/main/res/values/strings.xml
|
||||||
|
source_lang = en
|
||||||
|
type = ANDROID
|
||||||
|
minimum_perc = 50
|
||||||
|
|
||||||
|
[I2P.android_lib_client]
|
||||||
|
file_filter = client/src/main/res/values-<lang>/strings.xml
|
||||||
|
source_file = client/src/main/res/values/strings.xml
|
||||||
|
source_lang = en
|
||||||
|
type = ANDROID
|
||||||
|
minimum_perc = 50
|
||||||
|
|
@ -1,74 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="net.i2p.android.router"
|
|
||||||
android.versionCode="4208704"
|
|
||||||
android.versionName="0.8.7-4_b1-API8"
|
|
||||||
android:installLocation="preferExternal"
|
|
||||||
>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="8" />
|
|
||||||
|
|
||||||
<application android:label="@string/app_name"
|
|
||||||
android:icon="@drawable/ic_launcher_itoopie" >
|
|
||||||
<service android:name=".service.RouterService"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:icon="@drawable/ic_launcher_itoopie" />
|
|
||||||
<provider android:name=".provider.CacheProvider"
|
|
||||||
android:authorities="net.i2p.android.router" />
|
|
||||||
<activity android:name=".activity.MainActivity"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:icon="@drawable/ic_launcher_itoopie"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar"
|
|
||||||
android:launchMode="singleTop" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.NewsActivity"
|
|
||||||
android:label="I2P News"
|
|
||||||
android:configChanges="orientation|keyboardHidden"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar" >
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.TextResourceActivity"
|
|
||||||
android:label="I2P Information"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar" >
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.LicenseActivity"
|
|
||||||
android:label="I2P License Information"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar" >
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.WebActivity"
|
|
||||||
android:label="I2P Web Browser"
|
|
||||||
android:configChanges="orientation|keyboardHidden"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:host="*.i2p" android:scheme="http" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.SettingsActivity"
|
|
||||||
android:label="I2P Settings"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar"
|
|
||||||
android:launchMode="singleTop" >
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.AddressbookActivity"
|
|
||||||
android:label="I2P Address Book"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar"
|
|
||||||
android:launchMode="singleTop" >
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.LogActivity"
|
|
||||||
android:label="I2P Logs"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar" >
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activity.PeersActivity"
|
|
||||||
android:label="I2P Peers and Transport Status"
|
|
||||||
android.theme="@android:style/Theme.NoTitleBar"
|
|
||||||
android:launchMode="singleTop" >
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
159
README.md
Normal file
159
README.md
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
# I2P Android
|
||||||
|
|
||||||
|
## Build process
|
||||||
|
|
||||||
|
### Dependencies:
|
||||||
|
|
||||||
|
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||||
|
- Apache Ant 1.8.0 or higher
|
||||||
|
- I2P source
|
||||||
|
- Android SDK (tested with Rev 22.6.4 and platform-tools version 19.1)
|
||||||
|
- Android Support Repository
|
||||||
|
- Gradle 1.12
|
||||||
|
|
||||||
|
### Gradle
|
||||||
|
|
||||||
|
The build system is based on Gradle. There are several methods for setting Gradle up:
|
||||||
|
|
||||||
|
* It can be downloaded from [the Gradle website](http://www.gradle.org/downloads).
|
||||||
|
|
||||||
|
* Most distributions will have Gradle packages. Be careful to check the
|
||||||
|
provided version; Debian and Ubuntu have old versions in their main
|
||||||
|
repositories. There is a [PPA](https://launchpad.net/~cwchien/+archive/gradle)
|
||||||
|
for Ubuntu with the latest version of Gradle.
|
||||||
|
|
||||||
|
* A Gradle wrapper is provided in the codebase. It takes all the same commands
|
||||||
|
as the regular `gradle` command. The first time that any command is run, it
|
||||||
|
will automatically download, cache and use the correct version of Gradle.
|
||||||
|
This is the simplest way to get started with the codebase. To use it, replace
|
||||||
|
`gradle` with `./gradlew` (or `./gradlew.bat` on Windows) in the commands
|
||||||
|
below.
|
||||||
|
|
||||||
|
Gradle will pull dependencies over the clearnet by default. To send all Gradle
|
||||||
|
connections from your user over Tor, create a `gradle.properties` file in
|
||||||
|
`~/.gradle/` containing:
|
||||||
|
|
||||||
|
```
|
||||||
|
systemProp.socksProxyHost=localhost
|
||||||
|
systemProp.socksProxyPort=9150
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preparation
|
||||||
|
|
||||||
|
1. Download the Android SDK. The simplest method is to download [Android Studio](https://developer.android.com/sdk/installing/studio.html).
|
||||||
|
|
||||||
|
* If you are using an existing Android SDK, install the Android Support
|
||||||
|
Repository via the SDK Manager.
|
||||||
|
|
||||||
|
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
|
||||||
|
|
||||||
|
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
|
||||||
|
|
||||||
|
```
|
||||||
|
i2psrc=/path/to/i2p.i2p
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building from the command line
|
||||||
|
|
||||||
|
1. Create a `local.properties` file in `i2p.android.base` containing:
|
||||||
|
|
||||||
|
```
|
||||||
|
sdk.dir=/path/to/android-studio/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
2. `gradle assembleDebug`
|
||||||
|
|
||||||
|
3. The APK will be placed in `i2p.android.base/app/build/outputs/apk`.
|
||||||
|
|
||||||
|
### Building with Android Studio
|
||||||
|
|
||||||
|
1. Import `i2p.android.base` into Android Studio. (This creates the `local.properties` file automatically).
|
||||||
|
|
||||||
|
2. Build and run the app (`Shift+F10`).
|
||||||
|
|
||||||
|
### Signing release builds
|
||||||
|
|
||||||
|
1. Create a `signing.properties` file in `i2p.android.base` containing:
|
||||||
|
|
||||||
|
```
|
||||||
|
STORE_FILE=/path/to/android.keystore
|
||||||
|
STORE_PASSWORD=store.password
|
||||||
|
KEY_ALIAS=key.alias
|
||||||
|
KEY_PASSWORD=key.password
|
||||||
|
```
|
||||||
|
|
||||||
|
2. `gradle assembleRelease`
|
||||||
|
|
||||||
|
## Client library
|
||||||
|
|
||||||
|
### "Uploading" to a local file repository (to use a local build of the library in a project)
|
||||||
|
|
||||||
|
1. Add the following line to your `~/.gradle/gradle.properties`:
|
||||||
|
|
||||||
|
```
|
||||||
|
localFileRepoDir=/path/to/local/file/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
2. `gradle :client:uploadArchives`
|
||||||
|
|
||||||
|
3. Add the resulting directory to your project as a repository. For Gradle projects, add the following above any existing repositories (so it is checked first):
|
||||||
|
|
||||||
|
```
|
||||||
|
repositories {
|
||||||
|
flatDir {
|
||||||
|
name 'fileRepo'
|
||||||
|
dirs file('/path/to/local/file/repo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uploading to Maven Central via Sonatype OSSRH
|
||||||
|
|
||||||
|
1. Add the following lines to your `~/.gradle/gradle.properties` (filling in the blanks):
|
||||||
|
|
||||||
|
```
|
||||||
|
signing.keyId=
|
||||||
|
signing.password=
|
||||||
|
signing.secretKeyRingFile=/path/to/secring.gpg
|
||||||
|
ossrhUsername=
|
||||||
|
ossrhPassword=
|
||||||
|
```
|
||||||
|
|
||||||
|
2. `gradle :client:uploadArchives`
|
||||||
|
|
||||||
|
### Commands from the old build instructions that might be useful
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create the android 4.4 (API 19) virtual device
|
||||||
|
# (don't make a custom hardware profile)
|
||||||
|
../android-sdk-linux/tools/android create avd --name i2p --target android-19
|
||||||
|
|
||||||
|
# then run the emulator:
|
||||||
|
# This may take a LONG time the first time (half an hour or more)...
|
||||||
|
# Run the debugger to ensure it is making progress
|
||||||
|
# -no-boot-anim for faster boot
|
||||||
|
# -dns-server 8.8.8.8 if the router can't reseed
|
||||||
|
# ../android-sdk-linux/tools/emulator -avd i2p -no-boot-anim -dns-server 8.8.8.8 &
|
||||||
|
../android-sdk-linux/tools/emulator -avd i2p &
|
||||||
|
|
||||||
|
# or to talk to a real device in debug mode:
|
||||||
|
# You have to do this if you get a permission error -
|
||||||
|
# Stop ddms, unplug the device, do the following,
|
||||||
|
# then plug in the device, then start ddms
|
||||||
|
adb kill-server
|
||||||
|
sudo adb start-server
|
||||||
|
adb devices
|
||||||
|
|
||||||
|
# Anyway, with I2P installed, click on the I2P icon on your device and enjoy!
|
||||||
|
|
||||||
|
#other helpful commands
|
||||||
|
../android-sdk-linux/platform-tools/adb shell
|
||||||
|
../android-sdk-linux/platform-tools/adb pull /some/file/on/emulator some-local-dir/
|
||||||
|
|
||||||
|
# copy the Dev Tools app from the emulator to your device
|
||||||
|
adb -e pull /system/app/Development.apk ./Development.apk
|
||||||
|
adb -d install Development.apk
|
||||||
|
|
||||||
|
# reinstall an existing apk onto the emulator
|
||||||
|
adb -e install -r bin/I2PAndroid-debug.apk
|
||||||
|
```
|
75
README.txt
75
README.txt
@ -1,75 +0,0 @@
|
|||||||
These instructions are for a recent Android SDK (1.6 or later).
|
|
||||||
These instructions were last updated for SDK Tools Version 11 with
|
|
||||||
SDK Platform-tools Version 5, June 2011.
|
|
||||||
|
|
||||||
The i2p source must be installed in ../i2p.i2p,
|
|
||||||
or else add i2psrc=/path/to/source in the local.properties file.
|
|
||||||
|
|
||||||
=====================
|
|
||||||
|
|
||||||
#Download the SDK from http://developer.android.com/sdk/index.html
|
|
||||||
#Unzip the android SDK in ../
|
|
||||||
#So then the android tools will be in ../android-sdk-linux_86/tools/
|
|
||||||
#
|
|
||||||
# Run the GUI updater, which you must do to get an SDK Platform:
|
|
||||||
../android-sdk-linux_86/tools/android &
|
|
||||||
|
|
||||||
# now go to the available packages tab, check the box and click refresh,
|
|
||||||
# and download an SDK Platform
|
|
||||||
# Since I2P is configured to run on 1.1 or higher
|
|
||||||
# (API 2) download that one. Otherwise you must change the
|
|
||||||
# target in default.properties from android-2 to andriod-x
|
|
||||||
# where x is the API version.
|
|
||||||
|
|
||||||
# To run the debugger (ddms) you also need to download the
|
|
||||||
# "Android SDK Platform-Tools" package from the GUI updater.
|
|
||||||
|
|
||||||
# create a file local.properties with the following line (without the leading # of course):
|
|
||||||
# sdk.dir=/path/to/your/android-sdk-linux_86
|
|
||||||
# The old property was sdk-location=/path/to/your/android-sdk-linux_86
|
|
||||||
# but it changed in more recent tools.
|
|
||||||
|
|
||||||
# DO NOT create a new project or anything. It's all set up right here for you.
|
|
||||||
|
|
||||||
# Create the android 2.2 (API 8) virtual device
|
|
||||||
# (don't make a custom hardware profile)
|
|
||||||
../android-sdk-linux_86/tools/android create avd --name i2p --target 8
|
|
||||||
|
|
||||||
#then run the emulator:
|
|
||||||
# This may take a LONG time the first time (half an hour or more)...
|
|
||||||
# Run the debugger to ensure it is making progress
|
|
||||||
# -no-boot-anim for faster boot
|
|
||||||
# -dns-server 8.8.8.8 if the router can't reseed
|
|
||||||
#../android-sdk-linux_86/tools/emulator -avd i2p -no-boot-anim -dns-server 8.8.8.8 &
|
|
||||||
../android-sdk-linux_86/tools/emulator -avd i2p &
|
|
||||||
|
|
||||||
# or to talk to a real phone in debug mode:
|
|
||||||
# You have to do this if you get a permission error -
|
|
||||||
# Stop ddms, unplug the phone, do the following,
|
|
||||||
# then plug in the phone, then start ddms
|
|
||||||
adb kill-server
|
|
||||||
sudo adb start-server
|
|
||||||
adb devices
|
|
||||||
|
|
||||||
#then wait a couple minutes until the emulator is up
|
|
||||||
#then install the I2P app
|
|
||||||
ant install
|
|
||||||
|
|
||||||
#then run the debugger
|
|
||||||
../android-sdk-linux_86/tools/ddms &
|
|
||||||
|
|
||||||
#to rebuild and reinstall to emulator or phone:
|
|
||||||
ant reinstall
|
|
||||||
|
|
||||||
# Now click on the I2P icon on your phone!
|
|
||||||
|
|
||||||
#other helpful commands
|
|
||||||
../android-sdk-linux_86/platform-tools/adb shell
|
|
||||||
../android-sdk-linux_86/platform-tools/adb pull /some/file/on/emulator some-local-dir/
|
|
||||||
|
|
||||||
# copy the Dev Tools app from the emulator to your phone
|
|
||||||
adb -e pull /system/app/Development.apk ./Development.apk
|
|
||||||
adb -d install Development.apk
|
|
||||||
|
|
||||||
# reinstall an existing apk onto the emulator
|
|
||||||
adb -e install -r bin/I2PAndroid-debug.apk
|
|
44
TODO
Normal file
44
TODO
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Fixes
|
||||||
|
|
||||||
|
- Better addressbook column widths
|
||||||
|
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
|
||||||
|
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
|
||||||
|
|
||||||
|
# Short-term
|
||||||
|
|
||||||
|
- Disable uPnP when on cell networks
|
||||||
|
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
|
||||||
|
- Display release notes directly on new router version
|
||||||
|
- Text content
|
||||||
|
- Move help content from release notes to help page
|
||||||
|
- Rewrite release notes to be release-specific
|
||||||
|
- Fill out help page
|
||||||
|
- Fix release notes UI, either make back button use clear or add buttons
|
||||||
|
- NetDB tablet view fixes
|
||||||
|
- Refresh detail fragment when changing tab
|
||||||
|
- Move list to correct item when changing tab
|
||||||
|
- Create nav history when viewing RI from LS
|
||||||
|
- Include GeoIP db for country info
|
||||||
|
- Maybe change router-off mechanic for various pages? Enable as they become available?
|
||||||
|
- Add "copy (error) log" option
|
||||||
|
|
||||||
|
# Medium-term
|
||||||
|
|
||||||
|
- Expose log level overrides
|
||||||
|
- Improve graphs
|
||||||
|
- Show time on bottom axis
|
||||||
|
- Show fixed x range, not only available data
|
||||||
|
- Think about pan/zoom
|
||||||
|
- How to persist data across restarts?
|
||||||
|
- I2PTunnel
|
||||||
|
- Show all messages somewhere
|
||||||
|
- Improve detail page, expose advanced settings
|
||||||
|
- Add edit page
|
||||||
|
|
||||||
|
# Long-term
|
||||||
|
|
||||||
|
- Remote router support
|
||||||
|
- Implement a "router wrapper" that can represent a local or remote router
|
||||||
|
- Implement/use client APIs to talk to remote router
|
||||||
|
- I2CP
|
||||||
|
- I2PControl
|
158
app/build.gradle
Normal file
158
app/build.gradle
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
apply plugin: 'android'
|
||||||
|
apply plugin: 'witness'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
|
||||||
|
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 9
|
||||||
|
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
|
||||||
|
}
|
||||||
|
signingConfigs {
|
||||||
|
release
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
runProguard false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
productFlavors {
|
||||||
|
free {
|
||||||
|
applicationId 'net.i2p.android'
|
||||||
|
}
|
||||||
|
donate {
|
||||||
|
applicationId 'net.i2p.android.donate'
|
||||||
|
}
|
||||||
|
legacy {
|
||||||
|
applicationId 'net.i2p.android.router'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':routerjars')
|
||||||
|
compile project(':client')
|
||||||
|
compile 'com.android.support:support-v4:19.1.0'
|
||||||
|
compile 'com.android.support:appcompat-v7:19.1.0'
|
||||||
|
compile files('libs/androidplot-core-0.6.0.jar')
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyVerification {
|
||||||
|
verify = [
|
||||||
|
'com.android.support:support-v4:3f40fa7b3a4ead01ce15dce9453b061646e7fe2e7c51cb75ca01ee1e77037f3f',
|
||||||
|
'com.android.support:appcompat-v7:9df7637c5219202ddbbbf0924c2d5a9e6d64379166795a89d8f75d1e3f3372df',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ext.i2pbase = '../i2p.i2p'
|
||||||
|
def Properties props = new Properties()
|
||||||
|
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
||||||
|
|
||||||
|
if (propFile.canRead()) {
|
||||||
|
props.load(new FileInputStream(propFile))
|
||||||
|
|
||||||
|
if (props != null &&
|
||||||
|
props.containsKey('i2psrc')) {
|
||||||
|
i2pbase = props['i2psrc']
|
||||||
|
} else {
|
||||||
|
println 'local.properties found but some entries are missing'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println 'local.properties not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
task certificatesZip(type: Zip) {
|
||||||
|
archiveName = 'certificates_zip'
|
||||||
|
from files('' + i2pbase + '/installer/resources/certificates')
|
||||||
|
}
|
||||||
|
task copyI2PResources(type: Copy) {
|
||||||
|
into 'src/main'
|
||||||
|
into('res/drawable') {
|
||||||
|
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||||
|
}
|
||||||
|
into('res/raw') {
|
||||||
|
from(i2pbase + '/installer/resources/blocklist.txt') { rename {'blocklist_txt' } }
|
||||||
|
from(i2pbase + '/installer/resources/hosts.txt') { rename {'hosts_txt' } }
|
||||||
|
from('../LICENSE.txt') { rename {'license_app_txt' } }
|
||||||
|
from('../licenses/LICENSE-Apache2.0.txt') { rename {'license_apache20_txt' } }
|
||||||
|
from(i2pbase + '/licenses') {
|
||||||
|
include { elem ->
|
||||||
|
elem.name in [
|
||||||
|
'LICENSE-ElGamalDSA.txt',
|
||||||
|
'LICENSE-SHA256.txt',
|
||||||
|
'LICENSE-BSD.txt',
|
||||||
|
'LICENSE-SNTP.txt',
|
||||||
|
'LICENSE-LGPLv2.1.txt',
|
||||||
|
'LICENSE-InstallCert.txt',
|
||||||
|
'LICENSE-BlockFile.txt',
|
||||||
|
'LICENSE-GPLv2.txt',
|
||||||
|
'LICENSE-GPLv3.txt',
|
||||||
|
'LICENSE-LGPLv3.txt',
|
||||||
|
'LICENSE-FatCowIcons.txt',
|
||||||
|
'LICENSE-Addressbook.txt',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rename { String name ->
|
||||||
|
String part = name.substring(8, name.lastIndexOf('.txt'))
|
||||||
|
String.format('license_%s_txt',
|
||||||
|
part.toLowerCase(Locale.US).replace('.', '_'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
from certificatesZip
|
||||||
|
}
|
||||||
|
// For peers WebView
|
||||||
|
into('assets/themes/console/images') {
|
||||||
|
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||||
|
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
|
||||||
|
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
|
||||||
|
}
|
||||||
|
into ('assets/themes/console/light') {
|
||||||
|
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
|
||||||
|
}
|
||||||
|
into('assets/themes/console/light/images') {
|
||||||
|
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task cleanI2PResources(type: Delete) {
|
||||||
|
delete file('src/main/res/drawable/i2plogo.png')
|
||||||
|
delete fileTree('src/main/res/raw') {
|
||||||
|
include 'blocklist_txt'
|
||||||
|
include 'hosts_txt'
|
||||||
|
include 'license_*'
|
||||||
|
include 'certificates_zip'
|
||||||
|
}
|
||||||
|
delete fileTree('src/main/assets/themes/console/images')
|
||||||
|
delete file('src/main/assets/themes/console/light/console.css')
|
||||||
|
delete file('src/main/assets/themes/console/light/images/header.png')
|
||||||
|
}
|
||||||
|
preBuild.dependsOn copyI2PResources
|
||||||
|
clean.dependsOn cleanI2PResources
|
||||||
|
|
||||||
|
props = new Properties()
|
||||||
|
propFile = new File(project.rootDir, 'signing.properties')
|
||||||
|
|
||||||
|
if (propFile.canRead()) {
|
||||||
|
props.load(new FileInputStream(propFile))
|
||||||
|
|
||||||
|
if (props != null &&
|
||||||
|
props.containsKey('STORE_FILE') &&
|
||||||
|
props.containsKey('STORE_PASSWORD') &&
|
||||||
|
props.containsKey('KEY_ALIAS') &&
|
||||||
|
props.containsKey('KEY_PASSWORD')) {
|
||||||
|
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
|
||||||
|
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
|
||||||
|
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
|
||||||
|
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
|
||||||
|
} else {
|
||||||
|
println 'signing.properties found but some entries are missing'
|
||||||
|
android.buildTypes.release.signingConfig = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println 'signing.properties not found'
|
||||||
|
android.buildTypes.release.signingConfig = null
|
||||||
|
}
|
BIN
app/libs/androidplot-core-0.6.0.jar
Normal file
BIN
app/libs/androidplot-core-0.6.0.jar
Normal file
Binary file not shown.
37
app/src/donate/res/xml/settings_headers.xml
Normal file
37
app/src/donate/res/xml/settings_headers.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/settings_label_bandwidth_net">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="net" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/label_graphs">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="graphs" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/settings_label_logging">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="logging" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:title="@string/label_addressbook">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.donate"
|
||||||
|
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/settings_label_advanced">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="advanced" />
|
||||||
|
</header>
|
||||||
|
</preference-headers>
|
32
app/src/donate/res/xml/settings_headers_legacy.xml
Normal file
32
app/src/donate/res/xml/settings_headers_legacy.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<Preference android:title="@string/settings_label_bandwidth_net">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.donate"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_NET" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/label_graphs">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.donate"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_GRAPHS" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/settings_label_logging">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.donate"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_LOGGING" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/label_addressbook">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.donate"
|
||||||
|
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/settings_label_advanced">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.donate"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_ADVANCED" />
|
||||||
|
</Preference>
|
||||||
|
</PreferenceScreen>
|
37
app/src/legacy/res/xml/settings_headers.xml
Normal file
37
app/src/legacy/res/xml/settings_headers.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/settings_label_bandwidth_net">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="net" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/label_graphs">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="graphs" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/settings_label_logging">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="logging" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:title="@string/label_addressbook">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.router"
|
||||||
|
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||||
|
</header>
|
||||||
|
<header
|
||||||
|
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||||
|
android:title="@string/settings_label_advanced">
|
||||||
|
<extra
|
||||||
|
android:name="settings"
|
||||||
|
android:value="advanced" />
|
||||||
|
</header>
|
||||||
|
</preference-headers>
|
32
app/src/legacy/res/xml/settings_headers_legacy.xml
Normal file
32
app/src/legacy/res/xml/settings_headers_legacy.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<Preference android:title="@string/settings_label_bandwidth_net">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.router"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_NET" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/label_graphs">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.router"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_GRAPHS" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/settings_label_logging">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.router"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_LOGGING" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/label_addressbook">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.router"
|
||||||
|
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||||
|
</Preference>
|
||||||
|
<Preference android:title="@string/settings_label_advanced">
|
||||||
|
<intent
|
||||||
|
android:targetPackage="net.i2p.android.router"
|
||||||
|
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||||
|
android:action="net.i2p.android.router.PREFS_ADVANCED" />
|
||||||
|
</Preference>
|
||||||
|
</PreferenceScreen>
|
204
app/src/main/AndroidManifest.xml
Normal file
204
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="net.i2p.android.router"
|
||||||
|
android:installLocation="auto"
|
||||||
|
android:versionCode="4745219"
|
||||||
|
android:versionName="0.9.14.1-rc3">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="9"
|
||||||
|
android:targetSdkVersion="19" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:icon="@drawable/ic_launcher_itoopie"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.AppCompat">
|
||||||
|
<service
|
||||||
|
android:name=".service.RouterService"
|
||||||
|
android:icon="@drawable/ic_launcher_itoopie"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="net.i2p.android.router.service.IRouterState" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<provider
|
||||||
|
android:name=".provider.CacheProvider"
|
||||||
|
android:authorities="net.i2p.android" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:icon="@drawable/ic_launcher_itoopie"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTop">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="net.i2p.android.router.START_I2P" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".NewsActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:label="I2P News"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".HelpActivity"
|
||||||
|
android:label="Help"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".LicenseActivity"
|
||||||
|
android:label="I2P License Information"
|
||||||
|
android:parentActivityName=".HelpActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.HelpActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".web.WebActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:label="I2P Web Browser">
|
||||||
|
<!-- Disabled, this browser is not very secure
|
||||||
|
Temporarily enabled until an alternative browser is ready -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="*.i2p"
|
||||||
|
android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:label="I2P Settings"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".addressbook.AddressbookSettingsActivity"
|
||||||
|
android:label="I2P Addressbook Settings"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:parentActivityName=".addressbook.AddressbookActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".addressbook.AddressbookActivity"
|
||||||
|
android:label="Addressbook"
|
||||||
|
android:launchMode="singleTop">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.PICK" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.searchable"
|
||||||
|
android:resource="@xml/searchable_addressbook" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".addressbook.AddressbookAddWizardActivity"
|
||||||
|
android:label="Add new Destination"
|
||||||
|
android:parentActivityName=".addressbook.AddressbookActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
|
||||||
|
android:label="I2PTunnel"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
|
||||||
|
android:label="I2PTunnel"
|
||||||
|
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
|
||||||
|
android:label="Tunnel Creation Wizard"
|
||||||
|
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".log.LogActivity"
|
||||||
|
android:label="I2P Logs"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".log.LogDetailActivity"
|
||||||
|
android:label="Log Entry"
|
||||||
|
android:parentActivityName=".log.LogActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.log.LogActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".stats.RateGraphActivity"
|
||||||
|
android:label="Rate Graph"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".stats.PeersActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden"
|
||||||
|
android:label="I2P Peers and Transport Status"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".netdb.NetDbActivity"
|
||||||
|
android:label="NetDB"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".netdb.NetDbDetailActivity"
|
||||||
|
android:label="NetDB Detail"
|
||||||
|
android:parentActivityName=".netdb.NetDbActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="net.i2p.android.router.netdb.NetDbActivity" />
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* The following code was written by Matthew Wiggins
|
||||||
|
* and is released under the APACHE 2.0 license
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Slight modifications and bugfixes by Sponge <sponge@mail.i2p>
|
||||||
|
* These modifications are released under the WTFPL (any version)
|
||||||
|
*
|
||||||
|
* We don't need negative numbers yet, and may never need to.
|
||||||
|
*
|
||||||
|
* XML Usage example:
|
||||||
|
*
|
||||||
|
* <com.hlidskialf.android.preference.SeekBarPreference android:key="duration"
|
||||||
|
* android:title="Duration of something"
|
||||||
|
* android:summary="How long something will last"
|
||||||
|
* android:dialogMessage="Something duration"
|
||||||
|
* android:defaultValue="5"
|
||||||
|
* android:text=" minutes"
|
||||||
|
* android:max="60"
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.hlidskialf.android.preference;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.DialogPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
|
||||||
|
|
||||||
|
private static final String androidns = "http://schemas.android.com/apk/res/android";
|
||||||
|
private SeekBar mSeekBar;
|
||||||
|
private TextView mSplashText;
|
||||||
|
private TextView mValueText;
|
||||||
|
private Context mContext;
|
||||||
|
private String mDialogMessage, mSuffix;
|
||||||
|
private String mDefault = "0";
|
||||||
|
private int mMax = 0;
|
||||||
|
private int mValue = 0;
|
||||||
|
private int mDirection = LinearLayout.HORIZONTAL;
|
||||||
|
|
||||||
|
|
||||||
|
public SeekBarPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
mContext = context;
|
||||||
|
int dialogMessageR = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
|
||||||
|
mDialogMessage = (dialogMessageR == 0)
|
||||||
|
? attrs.getAttributeValue(androidns, "dialogMessage")
|
||||||
|
: context.getResources().getString(dialogMessageR);
|
||||||
|
int textR = attrs.getAttributeResourceValue(androidns, "text", 0);
|
||||||
|
mSuffix = (textR == 0)
|
||||||
|
? attrs.getAttributeValue(androidns, "text")
|
||||||
|
: context.getResources().getString(textR);
|
||||||
|
mDefault = attrs.getAttributeValue(androidns, "defaultValue");
|
||||||
|
mMax = Integer.parseInt(attrs.getAttributeValue(androidns, "max"));
|
||||||
|
if (attrs.getAttributeValue(androidns, "direction") != null) {
|
||||||
|
mDirection = Integer.parseInt(attrs.getAttributeValue(androidns, "direction"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View onCreateDialogView() {
|
||||||
|
LinearLayout.LayoutParams params;
|
||||||
|
LinearLayout layout = new LinearLayout(mContext);
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
layout.setPadding(6, 6, 6, 10);
|
||||||
|
|
||||||
|
// Set the width so that it is as usable as possible.
|
||||||
|
// We multiplymMax so that the smaller ranges will get a bigger area.
|
||||||
|
|
||||||
|
if (mDirection == LinearLayout.HORIZONTAL) {
|
||||||
|
layout.setMinimumWidth(mMax*5);
|
||||||
|
} else {
|
||||||
|
layout.setMinimumHeight(mMax*5);
|
||||||
|
}
|
||||||
|
|
||||||
|
mSplashText = new TextView(mContext);
|
||||||
|
if (mDialogMessage != null) {
|
||||||
|
mSplashText.setText(mDialogMessage);
|
||||||
|
}
|
||||||
|
layout.addView(mSplashText);
|
||||||
|
|
||||||
|
mValueText = new TextView(mContext);
|
||||||
|
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||||
|
mValueText.setTextSize(32);
|
||||||
|
params = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
layout.addView(mValueText, params);
|
||||||
|
|
||||||
|
mSeekBar = new SeekBar(mContext);
|
||||||
|
mSeekBar.setOnSeekBarChangeListener(this);
|
||||||
|
// Move the bar away from the changing text, so you can see it, and
|
||||||
|
// move it away from the edges to improve usability for the end-ranges.
|
||||||
|
mSeekBar.setPadding(6, 30, 6, 6);
|
||||||
|
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
if (shouldPersist()) {
|
||||||
|
mValue = Integer.parseInt(getPersistedString(mDefault));
|
||||||
|
}
|
||||||
|
mSeekBar.setMax(mMax);
|
||||||
|
mSeekBar.setProgress(mValue);
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindDialogView(View v) {
|
||||||
|
super.onBindDialogView(v);
|
||||||
|
mSeekBar.setMax(mMax);
|
||||||
|
mSeekBar.setProgress(mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSetInitialValue(boolean restore, Object defaultValue) {
|
||||||
|
super.onSetInitialValue(restore, defaultValue);
|
||||||
|
if (restore) {
|
||||||
|
mValue = shouldPersist() ? Integer.parseInt(getPersistedString(mDefault)) : 0;
|
||||||
|
} else {
|
||||||
|
mValue = (Integer) defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
|
||||||
|
String t = String.valueOf(value);
|
||||||
|
mValueText.setText(mSuffix == null ? t : t.concat(mSuffix));
|
||||||
|
if (shouldPersist()) {
|
||||||
|
persistString(t);
|
||||||
|
}
|
||||||
|
callChangeListener(Integer.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStartTrackingTouch(SeekBar seek) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopTrackingTouch(SeekBar seek) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMax(int max) {
|
||||||
|
mMax = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMax() {
|
||||||
|
return mMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(int progress) {
|
||||||
|
mValue = progress;
|
||||||
|
if (mSeekBar != null) {
|
||||||
|
mSeekBar.setProgress(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgress() {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,9 @@ package net.i2p.android.apps;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
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;
|
||||||
@ -47,7 +46,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
|||||||
_eepget.setWriteErrorToOutput();
|
_eepget.setWriteErrorToOutput();
|
||||||
//_eepget.addStatusListener(this);
|
//_eepget.addStatusListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes to output stream
|
* Writes to output stream
|
||||||
*/
|
*/
|
||||||
@ -62,7 +61,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
|||||||
if (writeErrorToStream)
|
if (writeErrorToStream)
|
||||||
_eepget.setWriteErrorToOutput();
|
_eepget.setWriteErrorToOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addStatusListener(EepGet.StatusListener l) {
|
public void addStatusListener(EepGet.StatusListener l) {
|
||||||
_eepget.addStatusListener(l);
|
_eepget.addStatusListener(l);
|
||||||
}
|
}
|
||||||
@ -149,7 +148,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
|||||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
|
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
|
||||||
|
|
||||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {}
|
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {}
|
||||||
|
|
||||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
|
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
|
||||||
|
|
||||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
@ -1,14 +1,10 @@
|
|||||||
package net.i2p.android.apps;
|
package net.i2p.android.apps;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
import net.i2p.android.router.NewsActivity;
|
||||||
|
import net.i2p.android.router.util.Notifications;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.router.Router;
|
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.util.RFC822Date;
|
import net.i2p.router.util.RFC822Date;
|
||||||
import net.i2p.util.EepGet;
|
import net.i2p.util.EepGet;
|
||||||
@ -22,6 +18,7 @@ import net.i2p.util.Translate;
|
|||||||
*/
|
*/
|
||||||
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||||
private final RouterContext _context;
|
private final RouterContext _context;
|
||||||
|
private final Notifications _notif;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
private long _lastFetch;
|
private long _lastFetch;
|
||||||
private long _lastUpdated;
|
private long _lastUpdated;
|
||||||
@ -30,31 +27,43 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
private File _newsFile;
|
private File _newsFile;
|
||||||
private File _tempFile;
|
private File _tempFile;
|
||||||
private static NewsFetcher _instance;
|
private static NewsFetcher _instance;
|
||||||
|
private volatile boolean _isRunning = true;
|
||||||
|
private Thread _thread;
|
||||||
|
|
||||||
public static final NewsFetcher getInstance() {
|
public static /*final */ NewsFetcher getInstance() {
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final synchronized NewsFetcher getInstance(RouterContext ctx) {
|
public static /* final */ synchronized NewsFetcher getInstance(
|
||||||
|
RouterContext ctx, Notifications notif) {
|
||||||
if (_instance != null)
|
if (_instance != null)
|
||||||
return _instance;
|
return _instance;
|
||||||
_instance = new NewsFetcher(ctx);
|
_instance = new NewsFetcher(ctx, notif);
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String NEWS_DIR = "docs";
|
private static final String NEWS_DIR = "docs";
|
||||||
private static final String NEWS_FILE = "news.xml";
|
private static final String NEWS_FILE = "news.xml";
|
||||||
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
||||||
/** @since 0.7.14 not configurable */
|
|
||||||
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
|
/**
|
||||||
|
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
|
||||||
|
* We may be able to change it to psi.i2p in a future release after
|
||||||
|
* the hostname propagates.
|
||||||
|
*
|
||||||
|
* @since 0.7.14 not configurable
|
||||||
|
*/
|
||||||
|
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml";
|
||||||
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
|
private static final String PROP_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";
|
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
|
||||||
|
|
||||||
private NewsFetcher(RouterContext ctx) {
|
private NewsFetcher(RouterContext ctx, Notifications notif) {
|
||||||
_context = ctx;
|
_context = ctx;
|
||||||
|
_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);
|
||||||
@ -68,7 +77,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
||||||
updateLastFetched();
|
updateLastFetched();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLastFetched() {
|
private void updateLastFetched() {
|
||||||
if (_newsFile.exists()) {
|
if (_newsFile.exists()) {
|
||||||
if (_lastUpdated == 0)
|
if (_lastUpdated == 0)
|
||||||
@ -83,7 +92,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
_lastModified = null;
|
_lastModified = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@ -100,17 +109,18 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final long INITIAL_DELAY = 5*60*1000;
|
private static final long INITIAL_DELAY = 5*60*1000;
|
||||||
private static final long RUN_DELAY = 30*60*1000;
|
private static final long RUN_DELAY = 30*60*1000;
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
|
_thread = Thread.currentThread();
|
||||||
try {
|
try {
|
||||||
Thread.sleep(INITIAL_DELAY);
|
Thread.sleep(INITIAL_DELAY);
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (true) {
|
while (_isRunning && _context.router().isAlive()) {
|
||||||
if (shouldFetchNews()) {
|
if (shouldFetchNews()) {
|
||||||
fetchNews();
|
fetchNews();
|
||||||
}
|
}
|
||||||
@ -121,7 +131,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldFetchNews() {
|
private boolean shouldFetchNews() {
|
||||||
if (_invalidated)
|
if (_invalidated)
|
||||||
return true;
|
return true;
|
||||||
@ -132,7 +142,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
long ms = Long.parseLong(freq);
|
long ms = Long.parseLong(freq);
|
||||||
if (ms <= 0)
|
if (ms <= 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (_lastFetch + ms < _context.clock().now()) {
|
if (_lastFetch + ms < _context.clock().now()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -162,10 +172,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
int proxyPort = 4444;
|
int proxyPort = 4444;
|
||||||
if (_tempFile.exists())
|
if (_tempFile.exists())
|
||||||
_tempFile.delete();
|
_tempFile.delete();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
EepGet get = null;
|
// EepGet get = null;
|
||||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
EepGet get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||||
get.addStatusListener(this);
|
get.addStatusListener(this);
|
||||||
if (get.fetch()) {
|
if (get.fetch()) {
|
||||||
_lastModified = get.getLastModified();
|
_lastModified = get.getLastModified();
|
||||||
@ -182,7 +192,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
_log.error("Error fetching the news", t);
|
_log.error("Error fetching the news", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -192,14 +202,17 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
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()) {
|
||||||
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
|
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
|
||||||
if (copied) {
|
if (copied) {
|
||||||
_lastUpdated = now;
|
_lastUpdated = now;
|
||||||
_tempFile.delete();
|
_tempFile.delete();
|
||||||
// notify somebody?
|
|
||||||
|
// Notify user
|
||||||
|
_notif.notify("News Updated", "Touch to view latest I2P news",
|
||||||
|
NewsActivity.class);
|
||||||
} else {
|
} else {
|
||||||
if (_log.shouldLog(Log.ERROR))
|
if (_log.shouldLog(Log.ERROR))
|
||||||
_log.error("Failed to copy the news file!");
|
_log.error("Failed to copy the news file!");
|
||||||
@ -212,7 +225,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
_context.router().setConfigSetting(PROP_LAST_CHECKED, "" + now);
|
_context.router().setConfigSetting(PROP_LAST_CHECKED, "" + now);
|
||||||
_context.router().saveConfig();
|
_context.router().saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@ -220,4 +233,12 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
|||||||
}
|
}
|
||||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||||
public void attempting(String url) {}
|
public void attempting(String url) {}
|
||||||
|
|
||||||
|
private class Shutdown implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
_isRunning = false;
|
||||||
|
if (_thread != null)
|
||||||
|
_thread.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class TunnelDetailActivity extends I2PActivityBase implements
|
||||||
|
TunnelDetailFragment.OnTunnelDeletedListener {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
||||||
|
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, detailFrag).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TunnelDetailFragment.OnTunnelDeletedListener
|
||||||
|
|
||||||
|
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class TunnelDetailFragment extends Fragment {
|
||||||
|
public static final String TUNNEL_ID = "tunnel_id";
|
||||||
|
|
||||||
|
OnTunnelDeletedListener mCallback;
|
||||||
|
private TunnelControllerGroup mGroup;
|
||||||
|
private TunnelEntry mTunnel;
|
||||||
|
|
||||||
|
public static TunnelDetailFragment newInstance(int tunnelId) {
|
||||||
|
TunnelDetailFragment f = new TunnelDetailFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(TUNNEL_ID, tunnelId);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface OnTunnelDeletedListener {
|
||||||
|
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mCallback = (OnTunnelDeletedListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement OnTunnelDeletedListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
String error;
|
||||||
|
try {
|
||||||
|
mGroup = TunnelControllerGroup.getInstance();
|
||||||
|
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
mGroup = null;
|
||||||
|
error = iae.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mGroup == null) {
|
||||||
|
// Show error
|
||||||
|
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
||||||
|
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
||||||
|
mTunnel = new TunnelEntry(getActivity(),
|
||||||
|
mGroup.getControllers().get(tunnelId),
|
||||||
|
tunnelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
|
||||||
|
|
||||||
|
if (mTunnel != null) {
|
||||||
|
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||||
|
name.setText(mTunnel.getName());
|
||||||
|
|
||||||
|
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
||||||
|
type.setText(mTunnel.getType());
|
||||||
|
|
||||||
|
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
||||||
|
description.setText(mTunnel.getDescription());
|
||||||
|
|
||||||
|
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
|
||||||
|
targetIfacePort.setText(mTunnel.getIfacePort());
|
||||||
|
|
||||||
|
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
|
||||||
|
accessIfacePort.setText(mTunnel.getIfacePort());
|
||||||
|
|
||||||
|
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
|
||||||
|
autoStart.setChecked(mTunnel.startAutomatically());
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_i2ptunnel_detail_actions, menu);
|
||||||
|
// Hide the edit action until we have an edit UI
|
||||||
|
menu.findItem(R.id.action_edit_tunnel).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareOptionsMenu(Menu menu) {
|
||||||
|
MenuItem start = menu.findItem(R.id.action_start_tunnel);
|
||||||
|
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
|
||||||
|
|
||||||
|
if (mTunnel != null) {
|
||||||
|
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
|
||||||
|
|
||||||
|
start.setVisible(isStopped);
|
||||||
|
start.setEnabled(isStopped);
|
||||||
|
|
||||||
|
stop.setVisible(!isStopped);
|
||||||
|
stop.setEnabled(!isStopped);
|
||||||
|
} else {
|
||||||
|
start.setVisible(false);
|
||||||
|
start.setEnabled(false);
|
||||||
|
|
||||||
|
stop.setVisible(false);
|
||||||
|
stop.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle presses on the action bar items
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_start_tunnel:
|
||||||
|
mTunnel.getController().startTunnelBackground();
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
|
||||||
|
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||||
|
// Reload the action bar to change the start/stop action
|
||||||
|
getActivity().supportInvalidateOptionsMenu();
|
||||||
|
return true;
|
||||||
|
case R.id.action_stop_tunnel:
|
||||||
|
mTunnel.getController().stopTunnel();
|
||||||
|
Toast.makeText(getActivity().getApplicationContext(),
|
||||||
|
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
|
||||||
|
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||||
|
// Reload the action bar to change the start/stop action
|
||||||
|
getActivity().supportInvalidateOptionsMenu();
|
||||||
|
return true;
|
||||||
|
case R.id.action_edit_tunnel:
|
||||||
|
return true;
|
||||||
|
case R.id.action_delete_tunnel:
|
||||||
|
DialogFragment dg = new DialogFragment() {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
return new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage(R.string.i2ptunnel_delete_confirm_message)
|
||||||
|
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
List<String> msgs = TunnelUtil.deleteTunnel(
|
||||||
|
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;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
224
app/src/main/java/net/i2p/android/i2ptunnel/TunnelEntry.java
Normal file
224
app/src/main/java/net/i2p/android/i2ptunnel/TunnelEntry.java
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.PrivateKeyFile;
|
||||||
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
|
||||||
|
public class TunnelEntry {
|
||||||
|
public static final int RUNNING = 1;
|
||||||
|
public static final int STARTING = 2;
|
||||||
|
public static final int NOT_RUNNING = 3;
|
||||||
|
public static final int STANDBY = 4;
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final TunnelController mController;
|
||||||
|
private final int mId;
|
||||||
|
|
||||||
|
public static TunnelEntry createNewTunnel(
|
||||||
|
Context ctx,
|
||||||
|
TunnelControllerGroup tcg,
|
||||||
|
TunnelConfig cfg) {
|
||||||
|
int tunnelId = tcg.getControllers().size();
|
||||||
|
List<String> msgs = TunnelUtil.saveTunnel(
|
||||||
|
ctx, tcg, -1, cfg.getConfig());
|
||||||
|
// TODO: Do something else with the other messages.
|
||||||
|
Toast.makeText(ctx.getApplicationContext(),
|
||||||
|
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||||
|
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
||||||
|
return new TunnelEntry(ctx, cur, tunnelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TunnelEntry(Context context, TunnelController controller, int id) {
|
||||||
|
mContext = context;
|
||||||
|
mController = controller;
|
||||||
|
mId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TunnelController getController() {
|
||||||
|
return mController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* General tunnel data for any type */
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
if (mController.getName() != null)
|
||||||
|
return mController.getName();
|
||||||
|
else
|
||||||
|
return mContext.getResources()
|
||||||
|
.getString(R.string.i2ptunnel_new_tunnel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInternalType() {
|
||||||
|
return mController.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return TunnelUtil.getTypeName(mController.getType(), mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
String rv = mController.getDescription();
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean startAutomatically() {
|
||||||
|
return mController.getStartOnLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
if (mController.getIsRunning()) {
|
||||||
|
if (isClient() && mController.getIsStandby())
|
||||||
|
return STANDBY;
|
||||||
|
else
|
||||||
|
return RUNNING;
|
||||||
|
} else if (mController.getIsStarting()) return STARTING;
|
||||||
|
else return NOT_RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClient() {
|
||||||
|
return TunnelUtil.isClient(mController.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Client tunnel data */
|
||||||
|
|
||||||
|
public boolean isSharedClient() {
|
||||||
|
return Boolean.parseBoolean(mController.getSharedClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientInterface() {
|
||||||
|
if ("streamrclient".equals(mController.getType()))
|
||||||
|
return mController.getTargetHost();
|
||||||
|
else
|
||||||
|
return mController.getListenOnInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientPort() {
|
||||||
|
String rv = mController.getListenPort();
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientDestination() {
|
||||||
|
String rv;
|
||||||
|
if ("client".equals(getInternalType()) ||
|
||||||
|
"ircclient".equals(getInternalType()) ||
|
||||||
|
"streamrclient".equals(getInternalType()))
|
||||||
|
rv = mController.getTargetDestination();
|
||||||
|
else
|
||||||
|
rv = mController.getProxyList();
|
||||||
|
return rv != null ? rv : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Server tunnel data */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to see if it is okay to linkify getServerTarget()
|
||||||
|
* @return true if getServerTarget() can be linkified, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isServerTargetLinkValid() {
|
||||||
|
return ("httpserver".equals(mController.getType()) ||
|
||||||
|
"httpbidirserver".equals(mController.getType())) &&
|
||||||
|
mController.getTargetHost() != null &&
|
||||||
|
mController.getTargetPort() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return valid host:port only if isServerTargetLinkValid() is true
|
||||||
|
*/
|
||||||
|
public String getServerTarget() {
|
||||||
|
String host;
|
||||||
|
if ("streamrserver".equals(getInternalType()))
|
||||||
|
host = mController.getListenOnInterface();
|
||||||
|
else
|
||||||
|
host = mController.getTargetHost();
|
||||||
|
String port = mController.getTargetPort();
|
||||||
|
if (host == null) host = "";
|
||||||
|
if (port == null) port = "";
|
||||||
|
if (host.indexOf(':') >= 0)
|
||||||
|
host = '[' + host + ']';
|
||||||
|
return host + ":" + port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestinationBase64() {
|
||||||
|
String rv = mController.getMyDestination();
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
// if not running, do this the hard way
|
||||||
|
String keyFile = mController.getPrivKeyFile();
|
||||||
|
if (keyFile != null && keyFile.trim().length() > 0) {
|
||||||
|
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
|
||||||
|
try {
|
||||||
|
Destination d = pkf.getDestination();
|
||||||
|
if (d != null)
|
||||||
|
return d.toBase64();
|
||||||
|
} catch (Exception e) {}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestHashBase32() {
|
||||||
|
String rv = mController.getMyDestHashBase32();
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data for some client and server tunnels */
|
||||||
|
|
||||||
|
/* Other output formats */
|
||||||
|
|
||||||
|
public String getIfacePort() {
|
||||||
|
if (isClient()) {
|
||||||
|
String host;
|
||||||
|
if ("streamrclient".equals(getInternalType()))
|
||||||
|
host = mController.getTargetHost();
|
||||||
|
else
|
||||||
|
host = mController.getListenOnInterface();
|
||||||
|
String port = mController.getListenPort();
|
||||||
|
if (host == null) host = "";
|
||||||
|
if (port == null) port = "";
|
||||||
|
return host + ":" + port;
|
||||||
|
} else return getServerTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetails() {
|
||||||
|
String details;
|
||||||
|
if (isClient())
|
||||||
|
details = getClientDestination();
|
||||||
|
else
|
||||||
|
details = "";
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getStatusIcon() {
|
||||||
|
switch (getStatus()) {
|
||||||
|
case STANDBY:
|
||||||
|
case STARTING:
|
||||||
|
return mContext.getResources()
|
||||||
|
.getDrawable(R.drawable.local_inprogress);
|
||||||
|
case RUNNING:
|
||||||
|
return mContext.getResources()
|
||||||
|
.getDrawable(R.drawable.local_up);
|
||||||
|
case NOT_RUNNING:
|
||||||
|
default:
|
||||||
|
return mContext.getResources()
|
||||||
|
.getDrawable(R.drawable.local_down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
|
||||||
|
private final LayoutInflater mInflater;
|
||||||
|
|
||||||
|
public TunnelEntryAdapter(Context context) {
|
||||||
|
super(context, android.R.layout.simple_list_item_2);
|
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<TunnelEntry> tunnels) {
|
||||||
|
clear();
|
||||||
|
if (tunnels != null) {
|
||||||
|
for (TunnelEntry tunnel : tunnels) {
|
||||||
|
add(tunnel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
|
||||||
|
TunnelEntry tunnel = getItem(position);
|
||||||
|
|
||||||
|
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||||
|
name.setText(tunnel.getName());
|
||||||
|
|
||||||
|
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
||||||
|
type.setText(tunnel.getType());
|
||||||
|
|
||||||
|
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
|
||||||
|
ifacePort.setText(tunnel.getIfacePort());
|
||||||
|
|
||||||
|
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
||||||
|
details.setText(tunnel.getDetails());
|
||||||
|
|
||||||
|
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||||
|
status.setImageDrawable(tunnel.getStatusIcon());
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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>> {
|
||||||
|
private TunnelControllerGroup mGroup;
|
||||||
|
private boolean mClientTunnels;
|
||||||
|
private List<TunnelEntry> mData;
|
||||||
|
private Handler mHandler;
|
||||||
|
private TunnelControllerMonitor mMonitor;
|
||||||
|
|
||||||
|
public TunnelEntryLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
|
||||||
|
super(context);
|
||||||
|
mGroup = tcg;
|
||||||
|
mClientTunnels = clientTunnels;
|
||||||
|
mHandler = new Handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TunnelEntry> loadInBackground() {
|
||||||
|
List<TunnelEntry> ret = new ArrayList<TunnelEntry>();
|
||||||
|
List<TunnelController> controllers = mGroup.getControllers();
|
||||||
|
for (int i = 0; i < controllers.size(); i++) {
|
||||||
|
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);
|
||||||
|
if ( (mClientTunnels && tunnel.isClient()) ||
|
||||||
|
(!mClientTunnels && !tunnel.isClient()) )
|
||||||
|
ret.add(tunnel);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(List<TunnelEntry> data) {
|
||||||
|
if (isReset()) {
|
||||||
|
// The Loader has been reset; ignore the result and invalidate the data.
|
||||||
|
if (data != null) {
|
||||||
|
releaseResources(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||||
|
// We must protect it until the new data has been delivered.
|
||||||
|
List<TunnelEntry> oldData = mData;
|
||||||
|
mData = data;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
// If the Loader is in a started state, have the superclass deliver the
|
||||||
|
// results to the client.
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the old data as we don't need it any more.
|
||||||
|
if (oldData != null && oldData != data) {
|
||||||
|
releaseResources(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mData != null) {
|
||||||
|
// Deliver any previously loaded data immediately.
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin monitoring the underlying data source.
|
||||||
|
mMonitor = new TunnelControllerMonitor();
|
||||||
|
mHandler.postDelayed(mMonitor, 50);
|
||||||
|
|
||||||
|
if (takeContentChanged() || mData == null) {
|
||||||
|
// When the observer detects a change, it should call onContentChanged()
|
||||||
|
// on the Loader, which will cause the next call to takeContentChanged()
|
||||||
|
// to return true. If this is ever the case (or if the current data is
|
||||||
|
// null), we force a new load.
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||||
|
// current load (if there is one).
|
||||||
|
cancelLoad();
|
||||||
|
|
||||||
|
// Note that we leave the observer as is. Loaders in a stopped state
|
||||||
|
// should still monitor the data source for changes so that the Loader
|
||||||
|
// will know to force a new load if it is ever started again.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
// Ensure the loader has been stopped.
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
// At this point we can release the resources associated with 'mData'.
|
||||||
|
if (mData != null) {
|
||||||
|
releaseResources(mData);
|
||||||
|
mData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Loader is being reset, so we should stop monitoring for changes.
|
||||||
|
if (mMonitor != null) {
|
||||||
|
mHandler.removeCallbacks(mMonitor);
|
||||||
|
mMonitor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(List<TunnelEntry> data) {
|
||||||
|
// Attempt to cancel the current asynchronous load.
|
||||||
|
super.onCanceled(data);
|
||||||
|
|
||||||
|
// The load has been canceled, so we should release the resources
|
||||||
|
// associated with 'data'.
|
||||||
|
releaseResources(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseResources(List<TunnelEntry> data) {
|
||||||
|
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||||
|
// would close it in this method. All resources associated with the Loader
|
||||||
|
// should be released here.
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TunnelControllerMonitor implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
// There is no way (yet) to monitor for changes to the list of
|
||||||
|
// TunnelControllers, so just force a refresh every 10 seconds.
|
||||||
|
onContentChanged();
|
||||||
|
mHandler.postDelayed(this, 10 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.app.ActionBar.Tab;
|
||||||
|
|
||||||
|
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_TAB = "selected_tab";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canUseTwoPanes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Set up action bar for tabs
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||||
|
|
||||||
|
// Client tunnels tab
|
||||||
|
TunnelListFragment cf = new TunnelListFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, true);
|
||||||
|
cf.setArguments(args);
|
||||||
|
Tab tab = actionBar.newTab()
|
||||||
|
.setText(R.string.label_i2ptunnel_client)
|
||||||
|
.setTabListener(new TabListener(cf));
|
||||||
|
actionBar.addTab(tab);
|
||||||
|
|
||||||
|
// Server tunnels tab
|
||||||
|
TunnelListFragment sf = new TunnelListFragment();
|
||||||
|
args = new Bundle();
|
||||||
|
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, false);
|
||||||
|
sf.setArguments(args);
|
||||||
|
tab = actionBar.newTab()
|
||||||
|
.setText(R.string.label_i2ptunnel_server)
|
||||||
|
.setTabListener(new TabListener(sf));
|
||||||
|
actionBar.addTab(tab);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
int selected = savedInstanceState.getInt(SELECTED_TAB);
|
||||||
|
actionBar.setSelectedNavigationItem(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// In two-pane mode, list items should be given the
|
||||||
|
// 'activated' state when touched.
|
||||||
|
cf.setActivateOnItemClick(true);
|
||||||
|
sf.setActivateOnItemClick(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt(SELECTED_TAB,
|
||||||
|
getSupportActionBar().getSelectedNavigationIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,274 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
||||||
|
import net.i2p.android.router.HelpActivity;
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
public class TunnelListFragment extends ListFragment implements
|
||||||
|
I2PFragmentBase.RouterContextUser,
|
||||||
|
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
|
||||||
|
public static final String SHOW_CLIENT_TUNNELS = "show_client_tunnels";
|
||||||
|
public static final String TUNNEL_WIZARD_DATA = "tunnel_wizard_data";
|
||||||
|
|
||||||
|
static final int TUNNEL_WIZARD_REQUEST = 1;
|
||||||
|
|
||||||
|
private static final int CLIENT_LOADER_ID = 1;
|
||||||
|
private static final int SERVER_LOADER_ID = 2;
|
||||||
|
/**
|
||||||
|
* The serialization (saved instance state) Bundle key representing the
|
||||||
|
* activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||||
|
|
||||||
|
private boolean mOnActivityCreated;
|
||||||
|
RouterContextProvider mRouterContextProvider;
|
||||||
|
OnTunnelSelectedListener mCallback;
|
||||||
|
private TunnelControllerGroup mGroup;
|
||||||
|
private TunnelEntryAdapter mAdapter;
|
||||||
|
private boolean mClientTunnels;
|
||||||
|
/**
|
||||||
|
* The current activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||||
|
private boolean mActivateOnItemClick = false;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface OnTunnelSelectedListener {
|
||||||
|
public void onTunnelSelected(int tunnelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mRouterContextProvider = (RouterContextProvider) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement RouterContextProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mCallback = (OnTunnelSelectedListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement OnTunnelSelectedListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
// Restore the previously serialized activated item position.
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||||
|
setActivatedPosition(savedInstanceState
|
||||||
|
.getInt(STATE_ACTIVATED_POSITION));
|
||||||
|
}
|
||||||
|
|
||||||
|
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||||
|
// give items the 'activated' state when touched.
|
||||||
|
getListView().setChoiceMode(
|
||||||
|
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
|
||||||
|
: ListView.CHOICE_MODE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
mAdapter = new TunnelEntryAdapter(getActivity());
|
||||||
|
mClientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
|
||||||
|
|
||||||
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
|
mOnActivityCreated = true;
|
||||||
|
if (getRouterContext() != null)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
else
|
||||||
|
setEmptyText(getResources().getString(
|
||||||
|
R.string.router_not_running));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRouterConnectionReady() {
|
||||||
|
String error;
|
||||||
|
try {
|
||||||
|
mGroup = TunnelControllerGroup.getInstance();
|
||||||
|
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
mGroup = null;
|
||||||
|
error = iae.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mGroup == null) {
|
||||||
|
setEmptyText(error);
|
||||||
|
} else {
|
||||||
|
if (mClientTunnels)
|
||||||
|
setEmptyText("No configured client tunnels.");
|
||||||
|
else
|
||||||
|
setEmptyText("No configured server tunnels.");
|
||||||
|
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().initLoader(mClientTunnels ? CLIENT_LOADER_ID
|
||||||
|
: SERVER_LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||||
|
super.onListItemClick(parent, view, pos, id);
|
||||||
|
mCallback.onTunnelSelected(mAdapter.getItem(pos).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||||
|
// Serialize and persist the activated item position.
|
||||||
|
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
|
||||||
|
if (getRouterContext() == null) {
|
||||||
|
menu.findItem(R.id.action_add_tunnel).setVisible(false);
|
||||||
|
menu.findItem(R.id.action_start_all_tunnels).setVisible(false);
|
||||||
|
menu.findItem(R.id.action_stop_all_tunnels).setVisible(false);
|
||||||
|
menu.findItem(R.id.action_restart_all_tunnels).setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle presses on the action bar items
|
||||||
|
List<String> msgs;
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_add_tunnel:
|
||||||
|
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
|
||||||
|
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
|
||||||
|
return true;
|
||||||
|
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;
|
||||||
|
// TODO: Enable when Help page finished
|
||||||
|
//case R.id.action_i2ptunnel_help:
|
||||||
|
// Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||||
|
// hi.putExtra(HelpActivity.REFERRER, "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>>
|
||||||
|
|
||||||
|
public Loader<List<TunnelEntry>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new TunnelEntryLoader(getActivity(), mGroup, mClientTunnels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoadFinished(Loader<List<TunnelEntry>> loader,
|
||||||
|
List<TunnelEntry> data) {
|
||||||
|
if (loader.getId() == (mClientTunnels ?
|
||||||
|
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||||
|
mAdapter.setData(data);
|
||||||
|
|
||||||
|
if (isResumed()) {
|
||||||
|
setListShown(true);
|
||||||
|
} else {
|
||||||
|
setListShownNoAnimation(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
|
||||||
|
if (loader.getId() == (mClientTunnels ?
|
||||||
|
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||||
|
mAdapter.setData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
|
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 {
|
||||||
|
@Override
|
||||||
|
protected AbstractWizardModel onCreateModel() {
|
||||||
|
return new TunnelWizardModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DialogFragment onGetFinishWizardDialog() {
|
||||||
|
return new DialogFragment() {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
return new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage(R.string.i2ptunnel_wizard_submit_confirm_message)
|
||||||
|
.setPositiveButton(R.string.i2ptunnel_wizard_submit_confirm_button,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(TunnelListFragment.TUNNEL_WIZARD_DATA, mWizardModel.save());
|
||||||
|
setResult(Activity.RESULT_OK, result);
|
||||||
|
dialog.dismiss();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package net.i2p.android.i2ptunnel;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
|
import net.i2p.android.wizard.model.BranchPage;
|
||||||
|
import net.i2p.android.wizard.model.Conditional;
|
||||||
|
import net.i2p.android.wizard.model.I2PDestinationPage;
|
||||||
|
import net.i2p.android.wizard.model.PageList;
|
||||||
|
import net.i2p.android.wizard.model.SingleFixedBooleanPage;
|
||||||
|
import net.i2p.android.wizard.model.SingleFixedChoicePage;
|
||||||
|
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||||
|
|
||||||
|
public class TunnelWizardModel extends AbstractWizardModel {
|
||||||
|
public TunnelWizardModel(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PageList onNewRootPageList() {
|
||||||
|
Resources res = mContext.getResources();
|
||||||
|
Conditional cTunnelType = new Conditional();
|
||||||
|
Conditional cClientType = new Conditional();
|
||||||
|
Conditional cServerType = new Conditional();
|
||||||
|
|
||||||
|
return new PageList(
|
||||||
|
new BranchPage(this, res.getString(R.string.i2ptunnel_wizard_k_client_server))
|
||||||
|
.addBranch(res.getString(R.string.i2ptunnel_wizard_v_client),
|
||||||
|
new SingleFixedChoicePage(this, res.getString(R.string.i2ptunnel_wizard_k_type))
|
||||||
|
.setChoices(
|
||||||
|
res.getString(R.string.i2ptunnel_type_client),
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_ircclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_sockstunnel),
|
||||||
|
res.getString(R.string.i2ptunnel_type_socksirctunnel),
|
||||||
|
res.getString(R.string.i2ptunnel_type_connectclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_streamrclient))
|
||||||
|
.setRequired(true)
|
||||||
|
.makeConditional(cClientType))
|
||||||
|
.addBranch(res.getString(R.string.i2ptunnel_wizard_v_server),
|
||||||
|
new SingleFixedChoicePage(this, res.getString(R.string.i2ptunnel_wizard_k_type))
|
||||||
|
.setChoices(
|
||||||
|
res.getString(R.string.i2ptunnel_type_server),
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpserver),
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpbidirserver),
|
||||||
|
res.getString(R.string.i2ptunnel_type_ircserver),
|
||||||
|
res.getString(R.string.i2ptunnel_type_streamrserver))
|
||||||
|
.setRequired(true)
|
||||||
|
.makeConditional(cServerType))
|
||||||
|
.setRequired(true)
|
||||||
|
.makeConditional(cTunnelType),
|
||||||
|
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_name))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_name))
|
||||||
|
.setRequired(true),
|
||||||
|
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_desc))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_desc)),
|
||||||
|
|
||||||
|
new I2PDestinationPage(this, res.getString(R.string.i2ptunnel_wizard_k_dest))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_dest))
|
||||||
|
.setRequired(true)
|
||||||
|
.setEqualAnyCondition(cClientType,
|
||||||
|
res.getString(R.string.i2ptunnel_type_client),
|
||||||
|
res.getString(R.string.i2ptunnel_type_ircclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_streamrclient)),
|
||||||
|
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_outproxies))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_outproxies))
|
||||||
|
.setEqualAnyCondition(cClientType,
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_connectclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_sockstunnel),
|
||||||
|
res.getString(R.string.i2ptunnel_type_socksirctunnel)),
|
||||||
|
|
||||||
|
// Not set required because a default is specified.
|
||||||
|
// Otherwise user would need to edit the field to
|
||||||
|
// enable the Next button.
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_target_host))
|
||||||
|
.setDefault("127.0.0.1")
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_target_host))
|
||||||
|
.setEqualCondition(cClientType,
|
||||||
|
res.getString(R.string.i2ptunnel_type_streamrclient))
|
||||||
|
.setEqualAnyCondition(cServerType,
|
||||||
|
res.getString(R.string.i2ptunnel_type_server),
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpserver),
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpbidirserver),
|
||||||
|
res.getString(R.string.i2ptunnel_type_ircserver)),
|
||||||
|
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_target_port))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_target_port))
|
||||||
|
.setNumeric(true)
|
||||||
|
.setRequired(true)
|
||||||
|
.setEqualCondition(cTunnelType, res.getString(R.string.i2ptunnel_wizard_v_server)),
|
||||||
|
|
||||||
|
// Not set required because a default is specified.
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_reachable_on))
|
||||||
|
.setDefault("127.0.0.1")
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_reachable_on))
|
||||||
|
.setEqualAnyCondition(cClientType,
|
||||||
|
res.getString(R.string.i2ptunnel_type_client),
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_ircclient),
|
||||||
|
res.getString(R.string.i2ptunnel_type_sockstunnel),
|
||||||
|
res.getString(R.string.i2ptunnel_type_socksirctunnel),
|
||||||
|
res.getString(R.string.i2ptunnel_type_connectclient))
|
||||||
|
.setEqualAnyCondition(cServerType,
|
||||||
|
res.getString(R.string.i2ptunnel_type_httpbidirserver),
|
||||||
|
res.getString(R.string.i2ptunnel_type_streamrserver)),
|
||||||
|
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_binding_port))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_k_binding_port))
|
||||||
|
.setNumeric(true)
|
||||||
|
.setRequired(true)
|
||||||
|
.setEqualCondition(cTunnelType, res.getString(R.string.i2ptunnel_wizard_v_client))
|
||||||
|
.setEqualCondition(cServerType, res.getString(R.string.i2ptunnel_type_httpbidirserver)),
|
||||||
|
|
||||||
|
new SingleFixedBooleanPage(this, res.getString(R.string.i2ptunnel_wizard_k_auto_start))
|
||||||
|
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_auto_start))
|
||||||
|
.setRequired(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,668 @@
|
|||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
241
app/src/main/java/net/i2p/android/i2ptunnel/util/TunnelUtil.java
Normal file
241
app/src/main/java/net/i2p/android/i2ptunnel/util/TunnelUtil.java
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
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.res.Resources;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.i2ptunnel.TunnelController;
|
||||||
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
|
import net.i2p.util.FileUtil;
|
||||||
|
import net.i2p.util.SecureFile;
|
||||||
|
|
||||||
|
public abstract class TunnelUtil {
|
||||||
|
public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) {
|
||||||
|
if (tunnel < 0) return null;
|
||||||
|
if (tcg == null) return null;
|
||||||
|
List<TunnelController> controllers = tcg.getControllers();
|
||||||
|
if (controllers.size() > tunnel)
|
||||||
|
return controllers.get(tunnel);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> saveTunnel(Context ctx,
|
||||||
|
TunnelControllerGroup 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 */
|
||||||
|
|
||||||
|
public static String getTypeFromName(String typeName, Context ctx) {
|
||||||
|
Resources res = ctx.getResources();
|
||||||
|
if (res.getString(R.string.i2ptunnel_type_client).equals(typeName))
|
||||||
|
return "client";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_httpclient).equals(typeName))
|
||||||
|
return "httpclient";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_ircclient).equals(typeName))
|
||||||
|
return "ircclient";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_server).equals(typeName))
|
||||||
|
return "server";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_httpserver).equals(typeName))
|
||||||
|
return "httpserver";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_sockstunnel).equals(typeName))
|
||||||
|
return "sockstunnel";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_socksirctunnel).equals(typeName))
|
||||||
|
return "socksirctunnel";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_connectclient).equals(typeName))
|
||||||
|
return "connectclient";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_ircserver).equals(typeName))
|
||||||
|
return "ircserver";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_streamrclient).equals(typeName))
|
||||||
|
return "streamrclient";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_streamrserver).equals(typeName))
|
||||||
|
return "streamrserver";
|
||||||
|
else if (res.getString(R.string.i2ptunnel_type_httpbidirserver).equals(typeName))
|
||||||
|
return "httpbidirserver";
|
||||||
|
else
|
||||||
|
return typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTypeName(String type, Context context) {
|
||||||
|
Resources res = context.getResources();
|
||||||
|
if ("client".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_client);
|
||||||
|
else if ("httpclient".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_httpclient);
|
||||||
|
else if ("ircclient".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_ircclient);
|
||||||
|
else if ("server".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_server);
|
||||||
|
else if ("httpserver".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_httpserver);
|
||||||
|
else if ("sockstunnel".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_sockstunnel);
|
||||||
|
else if ("socksirctunnel".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
|
||||||
|
else if ("connectclient".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_connectclient);
|
||||||
|
else if ("ircserver".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_ircserver);
|
||||||
|
else if ("streamrclient".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_streamrclient);
|
||||||
|
else if ("streamrserver".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_streamrserver);
|
||||||
|
else if ("httpbidirserver".equals(type))
|
||||||
|
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
|
||||||
|
else
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isClient(String type) {
|
||||||
|
return ( ("client".equals(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) {
|
||||||
|
TunnelController tun = getController(tcg, tunnel);
|
||||||
|
if (tun != null && tun.getPrivKeyFile() != null)
|
||||||
|
return tun.getPrivKeyFile();
|
||||||
|
if (tunnel < 0)
|
||||||
|
tunnel = tcg == null ? 999 : tcg.getControllers().size();
|
||||||
|
return "i2ptunnel" + tunnel + "-privKeys.dat";
|
||||||
|
}
|
||||||
|
}
|
52
app/src/main/java/net/i2p/android/router/HelpActivity.java
Normal file
52
app/src/main/java/net/i2p/android/router/HelpActivity.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
public class HelpActivity extends I2PActivityBase {
|
||||||
|
public static final String REFERRER = "help_referrer";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
/*if (savedInstanceState == null) {
|
||||||
|
HelpFragment f = new HelpFragment();
|
||||||
|
f.setArguments(getIntent().getExtras());
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, f).commit();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_help_licenses:
|
||||||
|
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
|
||||||
|
startActivity(lic);
|
||||||
|
return true;
|
||||||
|
case R.id.menu_help_release_notes:
|
||||||
|
TextResourceDialog dialog = new TextResourceDialog();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
|
||||||
|
getResources().getString(R.string.label_release_notes));
|
||||||
|
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(getSupportFragmentManager(), "release_notes");
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
455
app/src/main/java/net/i2p/android/router/I2PActivityBase.java
Normal file
455
app/src/main/java/net/i2p/android/router/I2PActivityBase.java
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
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.ActionBarDrawerToggle;
|
||||||
|
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.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);
|
||||||
|
|
||||||
|
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,
|
||||||
|
android.R.layout.simple_list_item_1, activityTitles));
|
||||||
|
// Set the list's click listener
|
||||||
|
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||||
|
|
||||||
|
// Enable ActionBar app icon to behave as action to toggle nav drawer
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
|
||||||
|
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
|
||||||
|
R.drawable.ic_drawer, 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:
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
118
app/src/main/java/net/i2p/android/router/I2PFragmentBase.java
Normal file
118
app/src/main/java/net/i2p/android/router/I2PFragmentBase.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import net.i2p.router.CommSystemFacade;
|
||||||
|
import net.i2p.router.NetworkDatabaseFacade;
|
||||||
|
import net.i2p.router.Router;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.TunnelManagerFacade;
|
||||||
|
import net.i2p.router.peermanager.ProfileOrganizer;
|
||||||
|
import net.i2p.router.transport.FIFOBandwidthLimiter;
|
||||||
|
import net.i2p.stat.StatManager;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
public class I2PFragmentBase extends Fragment {
|
||||||
|
private boolean mOnActivityCreated;
|
||||||
|
RouterContextProvider mCallback;
|
||||||
|
|
||||||
|
public static final String PREF_INSTALLED_VERSION = "app.version";
|
||||||
|
|
||||||
|
public interface RouterContextUser {
|
||||||
|
public void onRouterBind();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface RouterContextProvider {
|
||||||
|
public RouterContext getRouterContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mCallback = (RouterContextProvider) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement RouterContextProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
mOnActivityCreated = true;
|
||||||
|
if (getRouterContext() != null)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
else
|
||||||
|
onRouterConnectionNotReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRouterBind() {
|
||||||
|
if (mOnActivityCreated)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** callback from I2PFragmentBase, override as necessary */
|
||||||
|
public void onRouterConnectionReady() {}
|
||||||
|
|
||||||
|
/** callback from I2PFragmentBase, override as necessary */
|
||||||
|
public void onRouterConnectionNotReady() {}
|
||||||
|
|
||||||
|
protected RouterContext getRouterContext() {
|
||||||
|
return mCallback.getRouterContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Router getRouter() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.router();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected NetworkDatabaseFacade getNetDb() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.netDb();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProfileOrganizer getProfileOrganizer() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.profileOrganizer();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TunnelManagerFacade getTunnelManager() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.tunnelManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CommSystemFacade getCommSystem() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.commSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected FIFOBandwidthLimiter getBandwidthLimiter() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.bandwidthLimiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StatManager getStatManager() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx == null)
|
||||||
|
return null;
|
||||||
|
return ctx.statManager();
|
||||||
|
}
|
||||||
|
}
|
304
app/src/main/java/net/i2p/android/router/InitActivities.java
Normal file
304
app/src/main/java/net/i2p/android/router/InitActivities.java
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.FileUtil;
|
||||||
|
import net.i2p.util.OrderedProperties;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// This needs to be changed so that we can have an alternative place
|
||||||
|
myDir = c.getFilesDir().getAbsolutePath();
|
||||||
|
_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
|
||||||
|
*/
|
||||||
|
public void mergeResourceToFile(int resID, String f, Properties overrides) {
|
||||||
|
InputStream in = null;
|
||||||
|
InputStream fin = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
in = ctx.getResources().openRawResource(resID);
|
||||||
|
Properties props = new OrderedProperties();
|
||||||
|
try {
|
||||||
|
fin = new FileInputStream(new File(myDir, f));
|
||||||
|
DataHelper.loadProps(props, fin);
|
||||||
|
Util.d("Merging resource into file " + f);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.d("Creating file " + f + " from resource");
|
||||||
|
}
|
||||||
|
|
||||||
|
// write in default settings
|
||||||
|
DataHelper.loadProps(props, in);
|
||||||
|
|
||||||
|
// override with user settings
|
||||||
|
if (overrides != null)
|
||||||
|
props.putAll(overrides);
|
||||||
|
File path = new File(myDir, f);
|
||||||
|
DataHelper.storeProps(props, path);
|
||||||
|
Util.d("Saved " + props.size() +" properties in " + f);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
} catch (Resources.NotFoundException nfe) {
|
||||||
|
} finally {
|
||||||
|
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||||
|
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for new version.
|
||||||
|
* FIXME we could just use shared prefs for this instead of storing in a file
|
||||||
|
* @return true if new version
|
||||||
|
*/
|
||||||
|
private boolean checkNewVersion() {
|
||||||
|
Properties props = new Properties();
|
||||||
|
|
||||||
|
InputStream fin = null;
|
||||||
|
try {
|
||||||
|
fin = ctx.openFileInput(CONFIG_FILE);
|
||||||
|
DataHelper.loadProps(props, fin);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.d("Looks like a new install");
|
||||||
|
} finally {
|
||||||
|
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldVersion = props.getProperty(PROP_INSTALLED_VERSION);
|
||||||
|
boolean newInstall = oldVersion == null;
|
||||||
|
boolean newVersion = !_ourVersion.equals(oldVersion);
|
||||||
|
|
||||||
|
if (newVersion) {
|
||||||
|
Util.d("New version " + _ourVersion);
|
||||||
|
props.setProperty(PROP_INSTALLED_VERSION, _ourVersion);
|
||||||
|
try {
|
||||||
|
DataHelper.storeProps(props, ctx.getFileStreamPath(CONFIG_FILE));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Util.d("Failed to write " + CONFIG_FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newVersion;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class LicenseActivity extends I2PActivityBase {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
// Start with the base view
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
LicenseFragment f = new LicenseFragment();
|
||||||
|
f.setArguments(getIntent().getExtras());
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, f).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||||
|
|
||||||
|
public class LicenseFragment extends ListFragment {
|
||||||
|
|
||||||
|
private static final String[] names = {
|
||||||
|
"Android Application License", "Apache 2.0",
|
||||||
|
"Router License Overview", "Blockfile", "Crypto Filters", "ElGamal / DSA",
|
||||||
|
"GPLv2", "LGPLv2.1", "GPLv3", "LGPLv3", "FatCowIcons",
|
||||||
|
"Ministreaming",
|
||||||
|
"InstallCert", "SHA-256", "SNTP", "Addressbook"};
|
||||||
|
|
||||||
|
private static final int[] files = {
|
||||||
|
R.raw.license_app_txt, R.raw.license_apache20_txt,
|
||||||
|
R.raw.licenses_txt, R.raw.license_blockfile_txt, R.raw.license_bsd_txt, R.raw.license_elgamaldsa_txt,
|
||||||
|
R.raw.license_gplv2_txt, R.raw.license_lgplv2_1_txt, R.raw.license_gplv3_txt, R.raw.license_lgplv3_txt,
|
||||||
|
R.raw.license_fatcowicons_txt, R.raw.license_bsd_txt,
|
||||||
|
R.raw.license_installcert_txt, R.raw.license_sha256_txt, R.raw.license_sntp_txt, R.raw.license_addressbook_txt};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, names));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||||
|
TextResourceDialog dialog = new TextResourceDialog();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE, names[pos]);
|
||||||
|
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, files[pos]);
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(getActivity().getSupportFragmentManager(), "license");
|
||||||
|
}
|
||||||
|
}
|
321
app/src/main/java/net/i2p/android/router/MainActivity.java
Normal file
321
app/src/main/java/net/i2p/android/router/MainActivity.java
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
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.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.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 (Util.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;
|
||||||
|
|
||||||
|
// TODO: Unhide when Help page finished
|
||||||
|
//case R.id.menu_help:
|
||||||
|
// Intent hi = new Intent(MainActivity.this, HelpActivity.class);
|
||||||
|
// hi.putExtra(HelpActivity.REFERRER, "main");
|
||||||
|
// startActivity(hi);
|
||||||
|
// return true;
|
||||||
|
|
||||||
|
// TODO: Remove when help page finished
|
||||||
|
case R.id.menu_help_licenses:
|
||||||
|
Intent lic = new Intent(MainActivity.this, LicenseActivity.class);
|
||||||
|
startActivity(lic);
|
||||||
|
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() && Util.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;
|
||||||
|
}
|
||||||
|
}
|
517
app/src/main/java/net/i2p/android/router/MainFragment.java
Normal file
517
app/src/main/java/net/i2p/android/router/MainFragment.java
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TableLayout;
|
||||||
|
import android.widget.TableRow;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
import net.i2p.android.router.dialog.FirstStartDialog;
|
||||||
|
import net.i2p.android.router.dialog.VersionDialog;
|
||||||
|
import net.i2p.android.router.service.State;
|
||||||
|
import net.i2p.android.router.util.LongToggleButton;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import net.i2p.data.LeaseSet;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.TunnelPoolSettings;
|
||||||
|
import net.i2p.util.Translate;
|
||||||
|
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MainFragment extends I2PFragmentBase {
|
||||||
|
|
||||||
|
private Handler _handler;
|
||||||
|
private Runnable _updater;
|
||||||
|
private Runnable _oneShotUpdate;
|
||||||
|
private String _savedStatus;
|
||||||
|
private boolean _keep = true;
|
||||||
|
private boolean _startPressed = false;
|
||||||
|
private static final String PREF_FIRST_START = "app.router.firstStart";
|
||||||
|
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
|
||||||
|
protected static final String PROP_NEW_INSTALL = "i2p.newInstall";
|
||||||
|
protected static final String PROP_NEW_VERSION = "i2p.newVersion";
|
||||||
|
RouterControlListener mCallback;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface RouterControlListener {
|
||||||
|
public boolean shouldShowOnOff();
|
||||||
|
public boolean shouldBeOn();
|
||||||
|
public void onStartRouterClicked();
|
||||||
|
public boolean onStopRouterClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mCallback = (RouterControlListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement RouterControlListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the fragment is first created.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
// Init stuff here so settings work.
|
||||||
|
if(savedInstanceState != null) {
|
||||||
|
String saved = savedInstanceState.getString("status");
|
||||||
|
if(saved != null) {
|
||||||
|
_savedStatus = saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_keep = true;
|
||||||
|
|
||||||
|
_handler = new Handler();
|
||||||
|
_updater = new Updater();
|
||||||
|
_oneShotUpdate = new OneShotUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_main, container, false);
|
||||||
|
|
||||||
|
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights);
|
||||||
|
lightImage.setImageResource(R.drawable.routerlogo_0);
|
||||||
|
|
||||||
|
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
|
||||||
|
b.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
boolean on = ((ToggleButton) view).isChecked();
|
||||||
|
if (on) {
|
||||||
|
_startPressed = true;
|
||||||
|
mCallback.onStartRouterClicked();
|
||||||
|
updateOneShot();
|
||||||
|
checkFirstStart();
|
||||||
|
} else {
|
||||||
|
if(mCallback.onStopRouterClicked()) {
|
||||||
|
updateOneShot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
_handler.removeCallbacks(_updater);
|
||||||
|
_handler.removeCallbacks(_oneShotUpdate);
|
||||||
|
if(_savedStatus != null) {
|
||||||
|
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
|
||||||
|
tv.setText(_savedStatus);
|
||||||
|
}
|
||||||
|
checkDialog();
|
||||||
|
_handler.postDelayed(_updater, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
_handler.removeCallbacks(_updater);
|
||||||
|
_handler.removeCallbacks(_oneShotUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
updateOneShot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
if(_savedStatus != null) {
|
||||||
|
outState.putString("status", _savedStatus);
|
||||||
|
}
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOneShot() {
|
||||||
|
_handler.postDelayed(_oneShotUpdate, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OneShotUpdate implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
updateVisibility();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Updater implements Runnable {
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private final int delay = 1000;
|
||||||
|
private final int toloop = delay / 500;
|
||||||
|
public void run() {
|
||||||
|
updateVisibility();
|
||||||
|
if(counter++ % toloop == 0) {
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
//_handler.postDelayed(this, 2500);
|
||||||
|
_handler.postDelayed(this, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateVisibility() {
|
||||||
|
boolean showOnOff = mCallback.shouldShowOnOff();
|
||||||
|
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
|
||||||
|
b.setVisibility(showOnOff ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
|
boolean isOn = mCallback.shouldBeOn();
|
||||||
|
b.setChecked(isOn);
|
||||||
|
|
||||||
|
if (showOnOff && !isOn) {
|
||||||
|
// Sometimes the final state message from the RouterService
|
||||||
|
// is not received. Ensure that the state image is correct.
|
||||||
|
// TODO: Fix the race between RouterService shutdown and
|
||||||
|
// IRouterState unbinding.
|
||||||
|
updateState(State.INIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onBackPressed() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
// RouterService svc = _routerService; Which is better to use?!
|
||||||
|
_keep = Util.isConnected(getActivity()) && (ctx != null || _startPressed);
|
||||||
|
Util.d("*********************************************************");
|
||||||
|
Util.d("Back pressed, Keep? " + _keep);
|
||||||
|
Util.d("*********************************************************");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if(!_keep) {
|
||||||
|
Thread t = new Thread(new KillMe());
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KillMe implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Util.d("*********************************************************");
|
||||||
|
Util.d("KillMe started!");
|
||||||
|
Util.d("*********************************************************");
|
||||||
|
try {
|
||||||
|
Thread.sleep(500); // is 500ms long enough?
|
||||||
|
} catch(InterruptedException ex) {
|
||||||
|
}
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState(State newState) {
|
||||||
|
final ImageView lightImage = (ImageView) getView().findViewById(R.id.main_lights);
|
||||||
|
if (newState == State.INIT ||
|
||||||
|
newState == State.STOPPED ||
|
||||||
|
newState == State.MANUAL_STOPPED ||
|
||||||
|
newState == State.MANUAL_QUITTED ||
|
||||||
|
newState == State.NETWORK_STOPPED) {
|
||||||
|
lightImage.setImageResource(R.drawable.routerlogo_0);
|
||||||
|
} else if (newState == State.STARTING ||
|
||||||
|
newState == State.STOPPING ||
|
||||||
|
newState == State.MANUAL_STOPPING ||
|
||||||
|
newState == State.MANUAL_QUITTING ||
|
||||||
|
newState == State.NETWORK_STOPPING) {
|
||||||
|
lightImage.setImageResource(R.drawable.routerlogo_1);
|
||||||
|
} else if (newState == State.RUNNING) {
|
||||||
|
lightImage.setImageResource(R.drawable.routerlogo_2);
|
||||||
|
} else if (newState == State.ACTIVE) {
|
||||||
|
lightImage.setImageResource(R.drawable.routerlogo_3);
|
||||||
|
} else if (newState == State.WAITING) {
|
||||||
|
lightImage.setImageResource(R.drawable.routerlogo_4);
|
||||||
|
} // Ignore unknown states.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatus() {
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
ScrollView sv = (ScrollView) getActivity().findViewById(R.id.main_scrollview);
|
||||||
|
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
|
||||||
|
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
|
||||||
|
|
||||||
|
if(!Util.isConnected(getActivity())) {
|
||||||
|
// Manually set state, RouterService won't be running
|
||||||
|
updateState(State.WAITING);
|
||||||
|
vStatusText.setText("No Internet connection is available");
|
||||||
|
vStatus.setVisibility(View.VISIBLE);
|
||||||
|
sv.setVisibility(View.VISIBLE);
|
||||||
|
} else if(ctx != null) {
|
||||||
|
if(_startPressed) {
|
||||||
|
_startPressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load running tunnels
|
||||||
|
loadDestinations(ctx);
|
||||||
|
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) {
|
||||||
|
short reach = ctx.commSystem().getReachabilityStatus();
|
||||||
|
int active = ctx.commSystem().countActivePeers();
|
||||||
|
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||||
|
int inEx = ctx.tunnelManager().getFreeTunnelCount();
|
||||||
|
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
||||||
|
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
||||||
|
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
|
||||||
|
int part = ctx.tunnelManager().getParticipatingCount();
|
||||||
|
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
|
||||||
|
String jobLag = DataHelper.formatDuration((long) dLag);
|
||||||
|
String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay());
|
||||||
|
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||||
|
|
||||||
|
String netstatus;
|
||||||
|
if (reach == net.i2p.router.CommSystemFacade.STATUS_DIFFERENT) {
|
||||||
|
netstatus = "Symmetric NAT";
|
||||||
|
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_HOSED) {
|
||||||
|
netstatus = "Port Failure";
|
||||||
|
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_OK) {
|
||||||
|
netstatus = "OK";
|
||||||
|
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_REJECT_UNSOLICITED) {
|
||||||
|
netstatus = "Firewalled";
|
||||||
|
} else {
|
||||||
|
netstatus = "Unknown";
|
||||||
|
}
|
||||||
|
String tunnelStatus = ctx.throttle().getTunnelStatus();
|
||||||
|
//ctx.commSystem().getReachabilityStatus();
|
||||||
|
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
|
||||||
|
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
|
||||||
|
|
||||||
|
// control total width
|
||||||
|
DecimalFormat fmt;
|
||||||
|
if(inBW >= 1000 || outBW >= 1000) {
|
||||||
|
fmt = new DecimalFormat("#0");
|
||||||
|
} else if(inBW >= 100 || outBW >= 100) {
|
||||||
|
fmt = new DecimalFormat("#0.0");
|
||||||
|
} else {
|
||||||
|
fmt = new DecimalFormat("#0.00");
|
||||||
|
}
|
||||||
|
|
||||||
|
double kBytesIn = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes() / 1024;
|
||||||
|
double kBytesOut = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes() / 1024;
|
||||||
|
|
||||||
|
// control total width
|
||||||
|
DecimalFormat kBfmt;
|
||||||
|
if(kBytesIn >= 1000 || kBytesOut >= 1000) {
|
||||||
|
kBfmt = new DecimalFormat("#0");
|
||||||
|
} else if(kBytesIn >= 100 || kBytesOut >= 100) {
|
||||||
|
kBfmt = new DecimalFormat("#0.0");
|
||||||
|
} else {
|
||||||
|
kBfmt = new DecimalFormat("#0.00");
|
||||||
|
}
|
||||||
|
|
||||||
|
String status =
|
||||||
|
"Network: " + netstatus
|
||||||
|
+ "\nPeers active/known: " + active + " / " + known
|
||||||
|
+ "\nExploratory Tunnels in/out: " + inEx + " / " + outEx
|
||||||
|
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
|
||||||
|
|
||||||
|
|
||||||
|
// Need to see if we have the participation option set to on.
|
||||||
|
// I thought there was a router method for that? I guess not! WHY NOT?
|
||||||
|
// It would be easier if we had a number to test status.
|
||||||
|
String participate = "\nParticipation: " + tunnelStatus +" (" + part + ")";
|
||||||
|
|
||||||
|
String details =
|
||||||
|
"\nBandwidth in/out: " + fmt.format(inBW) + " / " + fmt.format(outBW) + " KBps"
|
||||||
|
+ "\nData usage in/out: " + kBfmt.format(kBytesIn) + " / " + kBfmt.format(kBytesOut) + " KB"
|
||||||
|
+ "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
|
||||||
|
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
|
||||||
|
+ "\nJob Lag: " + jobLag
|
||||||
|
+ "\nMsg Delay: " + msgDelay
|
||||||
|
+ "\nUptime: " + uptime;
|
||||||
|
|
||||||
|
_savedStatus = status + participate + details;
|
||||||
|
vStatusText.setText(_savedStatus);
|
||||||
|
vStatus.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
vStatus.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
sv.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
// network but no router context
|
||||||
|
vStatusText.setText("Not running");
|
||||||
|
sv.setVisibility(View.INVISIBLE);
|
||||||
|
/**
|
||||||
|
* **
|
||||||
|
* RouterService svc = _routerService; String status = "connected? "
|
||||||
|
* + Util.isConnected(this) + "\nMemory: " +
|
||||||
|
* DataHelper.formatSize(Runtime.getRuntime().totalMemory() -
|
||||||
|
* Runtime.getRuntime().freeMemory()) + "B / " +
|
||||||
|
* DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B' +
|
||||||
|
* "\nhave ctx? " + (ctx != null) + "\nhave svc? " + (svc != null) +
|
||||||
|
* "\nis bound? " + _isBound + "\nsvc state: " + (svc == null ?
|
||||||
|
* "null" : svc.getState()) + "\ncan start? " + (svc == null ?
|
||||||
|
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
|
||||||
|
* "null" : svc.canManualStop()); tv.setText(status);
|
||||||
|
* tv.setVisibility(View.VISIBLE);
|
||||||
|
***
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on net.i2p.router.web.SummaryHelper.getDestinations()
|
||||||
|
* @param ctx The RouterContext
|
||||||
|
*/
|
||||||
|
private void loadDestinations(RouterContext ctx) {
|
||||||
|
TableLayout dests = (TableLayout) getView().findViewById(R.id.main_tunnels);
|
||||||
|
dests.removeAllViews();
|
||||||
|
|
||||||
|
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients());
|
||||||
|
if (!clients.isEmpty()) {
|
||||||
|
Collections.sort(clients, new AlphaComparator(ctx));
|
||||||
|
for (Destination client : clients) {
|
||||||
|
String name = getName(ctx, client);
|
||||||
|
Hash h = client.calculateHash();
|
||||||
|
TableRow dest = new TableRow(getActivity());
|
||||||
|
//dest.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
|
// Client or server
|
||||||
|
ImageView type = new ImageView(getActivity());
|
||||||
|
type.setPadding(6, 6, 6, 6);
|
||||||
|
if (ctx.clientManager().shouldPublishLeaseSet(h))
|
||||||
|
type.setImageDrawable(getActivity().getResources()
|
||||||
|
.getDrawable(R.drawable.server));
|
||||||
|
else
|
||||||
|
type.setImageDrawable(getActivity().getResources()
|
||||||
|
.getDrawable(R.drawable.client));
|
||||||
|
dest.addView(type);
|
||||||
|
|
||||||
|
// Name
|
||||||
|
TextView destName = new TextView(getActivity());
|
||||||
|
destName.setPadding(6, 0, 0, 0);
|
||||||
|
destName.setText(name);
|
||||||
|
//destName.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
|
||||||
|
dest.addView(destName);
|
||||||
|
|
||||||
|
// Status
|
||||||
|
LeaseSet ls = ctx.netDb().lookupLeaseSetLocally(h);
|
||||||
|
if (ls != null && ctx.tunnelManager().getOutboundClientTunnelCount(h) > 0) {
|
||||||
|
long timeToExpire = ls.getEarliestLeaseDate() - ctx.clock().now();
|
||||||
|
if (timeToExpire < 0) {
|
||||||
|
// red or yellow light
|
||||||
|
type.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
} else {
|
||||||
|
// green light
|
||||||
|
type.setBackgroundColor(Color.GREEN);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// yellow light
|
||||||
|
type.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
dests.addView(dest);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TableRow empty = new TableRow(getActivity());
|
||||||
|
TextView emptyText = new TextView(getActivity());
|
||||||
|
emptyText.setText("No client tunnels are running yet.");
|
||||||
|
empty.addView(emptyText);
|
||||||
|
dests.addView(empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** compare translated nicknames - put "shared clients" first in the sort */
|
||||||
|
private class AlphaComparator implements Comparator<Destination> {
|
||||||
|
private String xsc;
|
||||||
|
private RouterContext _ctx;
|
||||||
|
|
||||||
|
public AlphaComparator(RouterContext ctx) {
|
||||||
|
_ctx = ctx;
|
||||||
|
xsc = _(ctx, "shared clients");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(Destination lhs, Destination rhs) {
|
||||||
|
String lname = getName(_ctx, lhs);
|
||||||
|
String rname = getName(_ctx, rhs);
|
||||||
|
if (lname.equals(xsc))
|
||||||
|
return -1;
|
||||||
|
if (rname.equals(xsc))
|
||||||
|
return 1;
|
||||||
|
return Collator.getInstance().compare(lname, rname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** translate here so collation works above */
|
||||||
|
private String getName(RouterContext ctx, Destination d) {
|
||||||
|
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
|
||||||
|
String name = (in != null ? in.getDestinationNickname() : null);
|
||||||
|
if (name == null) {
|
||||||
|
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
|
||||||
|
name = (out != null ? out.getDestinationNickname() : null);
|
||||||
|
if (name == null)
|
||||||
|
name = d.calculateHash().toBase64().substring(0,6);
|
||||||
|
else
|
||||||
|
name = _(ctx, name);
|
||||||
|
} else {
|
||||||
|
name = _(ctx, name);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String _(RouterContext ctx, String s) {
|
||||||
|
return Translate.getString(s, ctx, "net.i2p.router.web.messages");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDialog() {
|
||||||
|
VersionDialog dialog = new VersionDialog();
|
||||||
|
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
|
||||||
|
if(oldVersion.equals("??")) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_INSTALL);
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(getActivity().getSupportFragmentManager(), "newinstall");
|
||||||
|
} else {
|
||||||
|
String currentVersion = Util.getOurVersion(getActivity());
|
||||||
|
if(!oldVersion.equals(currentVersion)) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_VERSION);
|
||||||
|
dialog.setArguments(args);
|
||||||
|
dialog.show(getActivity().getSupportFragmentManager(), "newversion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFirstStart() {
|
||||||
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
|
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
||||||
|
if (firstStart) {
|
||||||
|
FirstStartDialog dialog = new FirstStartDialog();
|
||||||
|
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
|
||||||
|
ab.setPref(PREF_FIRST_START, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
app/src/main/java/net/i2p/android/router/NewsActivity.java
Normal file
19
app/src/main/java/net/i2p/android/router/NewsActivity.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class NewsActivity extends I2PActivityBase {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
// Start with the base view
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
NewsFragment f = new NewsFragment();
|
||||||
|
f.setArguments(getIntent().getExtras());
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, f).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,23 @@
|
|||||||
package net.i2p.android.router.activity;
|
package net.i2p.android.router;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.KeyEvent;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
import net.i2p.android.router.R;
|
|
||||||
import net.i2p.android.apps.NewsFetcher;
|
import net.i2p.android.apps.NewsFetcher;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.web.I2PWebViewClient;
|
||||||
|
|
||||||
public class NewsActivity extends I2PActivityBase {
|
public class NewsFragment extends I2PFragmentBase {
|
||||||
|
|
||||||
private I2PWebViewClient _wvClient;
|
private I2PWebViewClient _wvClient;
|
||||||
private long _lastChanged;
|
private long _lastChanged;
|
||||||
@ -32,17 +32,18 @@ public class NewsActivity extends I2PActivityBase {
|
|||||||
private static final String FOOTER = "</body></html>";
|
private static final String FOOTER = "</body></html>";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState)
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
super.onCreate(savedInstanceState);
|
View v = inflater.inflate(R.layout.fragment_news, container, false);
|
||||||
setContentView(R.layout.news);
|
WebView wv = (WebView) v.findViewById(R.id.news_webview);
|
||||||
WebView wv = (WebView) findViewById(R.id.news_webview);
|
|
||||||
wv.getSettings().setLoadsImagesAutomatically(false);
|
wv.getSettings().setLoadsImagesAutomatically(false);
|
||||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||||
wv.getSettings().setUseWideViewPort(true);
|
wv.getSettings().setUseWideViewPort(true);
|
||||||
_wvClient = new I2PWebViewClient(this);
|
_wvClient = new I2PWebViewClient();
|
||||||
wv.setWebViewClient(_wvClient);
|
wv.setWebViewClient(_wvClient);
|
||||||
wv.getSettings().setBuiltInZoomControls(true);
|
wv.getSettings().setBuiltInZoomControls(true);
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -52,18 +53,19 @@ public class NewsActivity extends I2PActivityBase {
|
|||||||
NewsFetcher nf = NewsFetcher.getInstance();
|
NewsFetcher nf = NewsFetcher.getInstance();
|
||||||
if (nf != null) {
|
if (nf != null) {
|
||||||
// always update the text
|
// always update the text
|
||||||
TextView tv = (TextView) findViewById(R.id.news_status);
|
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
|
||||||
tv.setText(WARNING + nf.status().replace(" ", " "));
|
tv.setText(WARNING + nf.status().replace(" ", " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
// only update the webview if we need to
|
// only update the webview if we need to
|
||||||
File newsFile = new File(_myDir, "docs/news.xml");
|
// XXX Gets dir directly instead of the one stored in the Activity (for now)
|
||||||
|
File newsFile = new File(getActivity().getFilesDir().getAbsolutePath(), "docs/news.xml");
|
||||||
boolean newsExists = newsFile.exists();
|
boolean newsExists = newsFile.exists();
|
||||||
if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
|
if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
|
||||||
return;
|
return;
|
||||||
_lastChanged = System.currentTimeMillis();
|
_lastChanged = System.currentTimeMillis();
|
||||||
|
|
||||||
WebView wv = (WebView) findViewById(R.id.news_webview);
|
WebView wv = (WebView) getActivity().findViewById(R.id.news_webview);
|
||||||
|
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
|
||||||
@ -75,14 +77,14 @@ public class NewsActivity extends I2PActivityBase {
|
|||||||
} else {
|
} else {
|
||||||
in = getResources().openRawResource(R.raw.initialnews_html);
|
in = getResources().openRawResource(R.raw.initialnews_html);
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = 0;
|
int read;
|
||||||
while ( (read = in.read(buf)) != -1)
|
while ( (read = in.read(buf)) != -1)
|
||||||
out.write(buf, 0, read);
|
out.write(buf, 0, read);
|
||||||
|
|
||||||
if (newsExists)
|
if (newsExists)
|
||||||
out.write(FOOTER.getBytes());
|
out.write(FOOTER.getBytes());
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
System.err.println("news error " + ioe);
|
System.err.println("news error " + ioe);
|
||||||
} catch (Resources.NotFoundException nfe) {
|
} catch (Resources.NotFoundException nfe) {
|
||||||
@ -96,17 +98,14 @@ public class NewsActivity extends I2PActivityBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public boolean onBackPressed() {
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
WebView wv = (WebView) getActivity().findViewById(R.id.news_webview);
|
||||||
WebView wv = (WebView) findViewById(R.id.news_webview);
|
_wvClient.cancelAll();
|
||||||
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
|
wv.stopLoading();
|
||||||
_wvClient.cancelAll();
|
if (wv.canGoBack()) {
|
||||||
wv.stopLoading();
|
wv.goBack();
|
||||||
if (wv.canGoBack()) {
|
return true;
|
||||||
wv.goBack();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
248
app/src/main/java/net/i2p/android/router/SettingsActivity.java
Normal file
248
app/src/main/java/net/i2p/android/router/SettingsActivity.java
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
package net.i2p.android.router;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.CheckBoxPreference;
|
||||||
|
import android.preference.PreferenceActivity;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
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 net.i2p.util.LogManager;
|
||||||
|
import net.i2p.util.OrderedProperties;
|
||||||
|
|
||||||
|
public class SettingsActivity extends PreferenceActivity {
|
||||||
|
// Actions for legacy settings
|
||||||
|
private static final String ACTION_PREFS_NET = "net.i2p.android.router.PREFS_NET";
|
||||||
|
public static final String ACTION_PREFS_GRAPHS = "net.i2p.android.router.PREFS_GRAPHS";
|
||||||
|
private static final String ACTION_PREFS_LOGGING = "net.i2p.android.router.PREFS_LOGGING";
|
||||||
|
private static final String ACTION_PREFS_ADVANCED = "net.i2p.android.router.PREFS_ADVANCED";
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
String action = getIntent().getAction();
|
||||||
|
if (action != null) {
|
||||||
|
if (ACTION_PREFS_NET.equals(action)) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_net);
|
||||||
|
} else if (ACTION_PREFS_GRAPHS.equals(action)){
|
||||||
|
addPreferencesFromResource(R.xml.settings_graphs);
|
||||||
|
setupGraphSettings(this, getPreferenceScreen(), getRouterContext());
|
||||||
|
} else if (ACTION_PREFS_LOGGING.equals(action)) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_logging);
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx != null)
|
||||||
|
setupLoggingSettings(this, getPreferenceScreen(), ctx);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static RouterContext getRouterContext() {
|
||||||
|
List<RouterContext> contexts = RouterContext.listContexts();
|
||||||
|
if ( !((contexts == null) || (contexts.isEmpty())) ) {
|
||||||
|
return contexts.get(0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
|
||||||
|
if (ctx == null) {
|
||||||
|
PreferenceCategory noRouter = new PreferenceCategory(context);
|
||||||
|
noRouter.setTitle(R.string.router_not_running);
|
||||||
|
ps.addPreference(noRouter);
|
||||||
|
} else if (StatSummarizer.instance() == null) {
|
||||||
|
PreferenceCategory noStats = new PreferenceCategory(context);
|
||||||
|
noStats.setTitle(R.string.stats_not_ready);
|
||||||
|
ps.addPreference(noStats);
|
||||||
|
} else {
|
||||||
|
StatManager mgr = ctx.statManager();
|
||||||
|
Map<String, SortedSet<String>> all = mgr.getStatsByGroup();
|
||||||
|
for (String group : all.keySet()) {
|
||||||
|
SortedSet<String> stats = all.get(group);
|
||||||
|
if (stats.size() == 0) continue;
|
||||||
|
PreferenceCategory groupPrefs = new PreferenceCategory(context);
|
||||||
|
groupPrefs.setKey("stat.groups." + group);
|
||||||
|
groupPrefs.setTitle(group);
|
||||||
|
ps.addPreference(groupPrefs);
|
||||||
|
for (String stat : stats) {
|
||||||
|
String key;
|
||||||
|
String description;
|
||||||
|
boolean canBeGraphed = false;
|
||||||
|
boolean currentIsGraphed = false;
|
||||||
|
RateStat rs = mgr.getRate(stat);
|
||||||
|
if (rs != null) {
|
||||||
|
description = rs.getDescription();
|
||||||
|
long period = rs.getPeriods()[0]; // should be the minimum
|
||||||
|
key = stat + "." + period;
|
||||||
|
if (period <= 10*60*1000) {
|
||||||
|
Rate r = rs.getRate(period);
|
||||||
|
canBeGraphed = r != null;
|
||||||
|
if (canBeGraphed) {
|
||||||
|
currentIsGraphed = r.getSummaryListener() != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FrequencyStat fs = mgr.getFrequency(stat);
|
||||||
|
if (fs != null) {
|
||||||
|
key = stat;
|
||||||
|
description = fs.getDescription();
|
||||||
|
// FrequencyStats cannot be graphed, but can be logged.
|
||||||
|
// XXX: Should log settings be here as well, or in a
|
||||||
|
// separate settings menu?
|
||||||
|
} else {
|
||||||
|
Util.e("Stat does not exist?! [" + stat + "]");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckBoxPreference statPref = new CheckBoxPreference(context);
|
||||||
|
statPref.setKey("stat.summaries." + key);
|
||||||
|
statPref.setTitle(stat);
|
||||||
|
statPref.setSummary(description);
|
||||||
|
statPref.setEnabled(canBeGraphed);
|
||||||
|
statPref.setChecked(currentIsGraphed);
|
||||||
|
groupPrefs.addPreference(statPref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void setupLoggingSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
|
||||||
|
if (ctx != null) {
|
||||||
|
LogManager mgr = ctx.logManager();
|
||||||
|
// Log level overrides
|
||||||
|
/*
|
||||||
|
StringBuilder buf = new StringBuilder(32*1024);
|
||||||
|
Properties limits = mgr.getLimits();
|
||||||
|
TreeSet<String> sortedLogs = new TreeSet<String>();
|
||||||
|
for (Iterator iter = limits.keySet().iterator(); iter.hasNext(); ) {
|
||||||
|
String prefix = (String)iter.next();
|
||||||
|
sortedLogs.add(prefix);
|
||||||
|
}
|
||||||
|
for (Iterator iter = sortedLogs.iterator(); iter.hasNext(); ) {
|
||||||
|
String prefix = (String)iter.next();
|
||||||
|
String level = limits.getProperty(prefix);
|
||||||
|
buf.append(prefix).append('=').append(level).append('\n');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} 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
|
||||||
|
protected void onPause() {
|
||||||
|
List<Properties> lProps = Util.getPropertiesFromPreferences(this);
|
||||||
|
Properties props = lProps.get(0);
|
||||||
|
Properties logSettings = lProps.get(1);
|
||||||
|
|
||||||
|
// Apply new config if we are running.
|
||||||
|
List<RouterContext> contexts = RouterContext.listContexts();
|
||||||
|
if ( !((contexts == null) || (contexts.isEmpty())) ) {
|
||||||
|
RouterContext _context = contexts.get(0);
|
||||||
|
_context.router().saveConfig(props, null);
|
||||||
|
|
||||||
|
// Merge in new log settings
|
||||||
|
saveLoggingChanges(_context, logSettings);
|
||||||
|
} else {
|
||||||
|
// Merge in new config settings, write the file.
|
||||||
|
InitActivities init = new InitActivities(this);
|
||||||
|
init.mergeResourceToFile(R.raw.router_config, "router.config", props);
|
||||||
|
|
||||||
|
// Merge in new log settings
|
||||||
|
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the settings in Android
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveLoggingChanges(I2PAppContext ctx, Properties logSettings) {
|
||||||
|
boolean shouldSave = false;
|
||||||
|
|
||||||
|
for (Object key : logSettings.keySet()) {
|
||||||
|
if ("logger.defaultLevel".equals(key)) {
|
||||||
|
String defaultLevel = (String) logSettings.get(key);
|
||||||
|
String oldDefault = ctx.logManager().getDefaultLimit();
|
||||||
|
if (!defaultLevel.equals(oldDefault)) {
|
||||||
|
shouldSave = true;
|
||||||
|
ctx.logManager().setDefaultLimit(defaultLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSave) {
|
||||||
|
ctx.logManager().saveConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
public static class SettingsFragment extends PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
String settings = getArguments().getString("settings");
|
||||||
|
if ("net".equals(settings)) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_net);
|
||||||
|
} else if ("graphs".equals(settings)) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_graphs);
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx != null)
|
||||||
|
setupGraphSettings(getActivity(), getPreferenceScreen(), ctx);
|
||||||
|
} else if ("logging".equals(settings)) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_logging);
|
||||||
|
RouterContext ctx = getRouterContext();
|
||||||
|
if (ctx != null)
|
||||||
|
setupLoggingSettings(getActivity(), getPreferenceScreen(), ctx);
|
||||||
|
} else if ("advanced".equals(settings)) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_advanced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isValidFragment(String fragmentName) {
|
||||||
|
return SettingsFragment.class.getName().equals(fragmentName);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
|
||||||
|
public class AddressEntry {
|
||||||
|
private final String mHostName;
|
||||||
|
private final Destination mDest;
|
||||||
|
|
||||||
|
public AddressEntry(String hostName, Destination dest) {
|
||||||
|
mHostName = hostName;
|
||||||
|
mDest = dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHostName() {
|
||||||
|
return mHostName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Destination getDestination() {
|
||||||
|
return mDest;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class AddressEntryAdapter extends ArrayAdapter<AddressEntry> {
|
||||||
|
private final LayoutInflater mInflater;
|
||||||
|
|
||||||
|
public AddressEntryAdapter(Context context) {
|
||||||
|
super(context, R.layout.addressbook_list_item);
|
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<AddressEntry> addresses) {
|
||||||
|
clear();
|
||||||
|
if (addresses != null) {
|
||||||
|
for (AddressEntry address : addresses) {
|
||||||
|
add(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View v = mInflater.inflate(R.layout.addressbook_list_item, parent, false);
|
||||||
|
AddressEntry address = getItem(position);
|
||||||
|
|
||||||
|
TextView text = (TextView) v.findViewById(R.id.text);
|
||||||
|
text.setText(address.getHostName());
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import net.i2p.android.router.util.NamingServiceUtil;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.client.naming.NamingService;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
||||||
|
private RouterContext mRContext;
|
||||||
|
private String mBook;
|
||||||
|
private String mFilter;
|
||||||
|
private List<AddressEntry> mData;
|
||||||
|
|
||||||
|
public AddressEntryLoader(Context context, RouterContext rContext,
|
||||||
|
String book, String filter) {
|
||||||
|
super(context);
|
||||||
|
mRContext = rContext;
|
||||||
|
mBook = book;
|
||||||
|
mFilter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AddressEntry> loadInBackground() {
|
||||||
|
// get the names
|
||||||
|
NamingService ns = NamingServiceUtil.getNamingService(mRContext, mBook);
|
||||||
|
Util.d("NamingService: " + ns.getName());
|
||||||
|
// After router shutdown we get nothing... why?
|
||||||
|
List<AddressEntry> ret = new ArrayList<AddressEntry>();
|
||||||
|
Map<String, Destination> names = new TreeMap<String, Destination>();
|
||||||
|
|
||||||
|
Properties searchProps = new Properties();
|
||||||
|
// Needed for HostsTxtNamingService
|
||||||
|
searchProps.setProperty("file", mBook);
|
||||||
|
if (mFilter != null && mFilter.length() > 0)
|
||||||
|
searchProps.setProperty("search", mFilter);
|
||||||
|
|
||||||
|
names.putAll(ns.getEntries(searchProps));
|
||||||
|
for (String hostName : names.keySet())
|
||||||
|
ret.add(new AddressEntry(hostName, names.get(hostName)));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(List<AddressEntry> data) {
|
||||||
|
if (isReset()) {
|
||||||
|
// The Loader has been reset; ignore the result and invalidate the data.
|
||||||
|
if (data != null) {
|
||||||
|
releaseResources(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||||
|
// We must protect it until the new data has been delivered.
|
||||||
|
List<AddressEntry> oldData = mData;
|
||||||
|
mData = data;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
// If the Loader is in a started state, have the superclass deliver the
|
||||||
|
// results to the client.
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the old data as we don't need it any more.
|
||||||
|
if (oldData != null && oldData != data) {
|
||||||
|
releaseResources(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mData != null) {
|
||||||
|
// Deliver any previously loaded data immediately.
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeContentChanged() || mData == null) {
|
||||||
|
// When the observer detects a change, it should call onContentChanged()
|
||||||
|
// on the Loader, which will cause the next call to takeContentChanged()
|
||||||
|
// to return true. If this is ever the case (or if the current data is
|
||||||
|
// null), we force a new load.
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||||
|
// current load (if there is one).
|
||||||
|
cancelLoad();
|
||||||
|
|
||||||
|
// Note that we leave the observer as is. Loaders in a stopped state
|
||||||
|
// should still monitor the data source for changes so that the Loader
|
||||||
|
// will know to force a new load if it is ever started again.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
// Ensure the loader has been stopped.
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
// At this point we can release the resources associated with 'mData'.
|
||||||
|
if (mData != null) {
|
||||||
|
releaseResources(mData);
|
||||||
|
mData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(List<AddressEntry> data) {
|
||||||
|
// Attempt to cancel the current asynchronous load.
|
||||||
|
super.onCanceled(data);
|
||||||
|
|
||||||
|
// The load has been canceled, so we should release the resources
|
||||||
|
// associated with 'data'.
|
||||||
|
releaseResources(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseResources(List<AddressEntry> data) {
|
||||||
|
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||||
|
// would close it in this method. All resources associated with the Loader
|
||||||
|
// should be released here.
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.web.WebActivity;
|
||||||
|
import net.i2p.android.router.web.WebFragment;
|
||||||
|
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.app.ActionBar;
|
||||||
|
import android.support.v7.app.ActionBar.Tab;
|
||||||
|
import android.support.v7.widget.SearchView;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
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_TAB = "selected_tab";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canUseTwoPanes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Set up action bar for tabs
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||||
|
|
||||||
|
// Router book tab
|
||||||
|
AddressbookFragment rf = new AddressbookFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(AddressbookFragment.BOOK_NAME,
|
||||||
|
AddressbookFragment.ROUTER_BOOK);
|
||||||
|
rf.setArguments(args);
|
||||||
|
Tab tab = actionBar.newTab()
|
||||||
|
.setText("Router")
|
||||||
|
.setTabListener(new TabListener(rf));
|
||||||
|
actionBar.addTab(tab);
|
||||||
|
|
||||||
|
// Private book tab
|
||||||
|
AddressbookFragment pf = new AddressbookFragment();
|
||||||
|
args = new Bundle();
|
||||||
|
args.putString(AddressbookFragment.BOOK_NAME,
|
||||||
|
AddressbookFragment.PRIVATE_BOOK);
|
||||||
|
pf.setArguments(args);
|
||||||
|
tab = actionBar.newTab()
|
||||||
|
.setText("Private")
|
||||||
|
.setTabListener(new TabListener(pf));
|
||||||
|
actionBar.addTab(tab);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
int selected = savedInstanceState.getInt(SELECTED_TAB);
|
||||||
|
actionBar.setSelectedNavigationItem(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt(SELECTED_TAB,
|
||||||
|
getSupportActionBar().getSelectedNavigationIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
// XXX: Temporarily reverting to inbuilt browser
|
||||||
|
// until an alternative browser is ready.
|
||||||
|
Intent i = new Intent(this, WebActivity.class);
|
||||||
|
i.putExtra(WebFragment.HTML_URI, "http://" + host + '/');
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchView.OnQueryTextListener
|
||||||
|
|
||||||
|
public boolean onQueryTextChange(String newText) {
|
||||||
|
filterAddresses(newText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onQueryTextSubmit(String query) {
|
||||||
|
filterAddresses(query);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filterAddresses(String query) {
|
||||||
|
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||||
|
if (f instanceof AddressbookFragment) {
|
||||||
|
AddressbookFragment af = (AddressbookFragment) f;
|
||||||
|
af.filterAddresses(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
|
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||||
|
|
||||||
|
public class AddressbookAddWizardActivity extends AbstractWizardActivity {
|
||||||
|
@Override
|
||||||
|
protected AbstractWizardModel onCreateModel() {
|
||||||
|
return new AddressbookAddWizardModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DialogFragment onGetFinishWizardDialog() {
|
||||||
|
return new DialogFragment() {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
return new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage("Add to private addressbook?")
|
||||||
|
.setPositiveButton("Add",
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
Intent result = new Intent();
|
||||||
|
setResult(Activity.RESULT_OK, result);
|
||||||
|
result.putExtra(AddressbookFragment.ADD_WIZARD_DATA, mWizardModel.save());
|
||||||
|
dialog.dismiss();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||||
|
import net.i2p.android.wizard.model.I2PB64DestinationPage;
|
||||||
|
import net.i2p.android.wizard.model.PageList;
|
||||||
|
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||||
|
|
||||||
|
public class AddressbookAddWizardModel extends AbstractWizardModel {
|
||||||
|
public AddressbookAddWizardModel(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PageList onNewRootPageList() {
|
||||||
|
Resources res = mContext.getResources();
|
||||||
|
|
||||||
|
return new PageList(
|
||||||
|
new SingleTextFieldPage(this, res.getString(R.string.addressbook_add_wizard_k_name))
|
||||||
|
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_name))
|
||||||
|
.setRequired(true),
|
||||||
|
|
||||||
|
new I2PB64DestinationPage(this, res.getString(R.string.addressbook_add_wizard_k_destination))
|
||||||
|
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_destination))
|
||||||
|
.setRequired(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,259 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.addressbook.Daemon;
|
||||||
|
import net.i2p.android.router.HelpActivity;
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||||
|
import net.i2p.android.router.util.NamingServiceUtil;
|
||||||
|
import net.i2p.client.naming.NamingService;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
public class AddressbookFragment extends ListFragment implements
|
||||||
|
I2PFragmentBase.RouterContextUser,
|
||||||
|
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
|
||||||
|
public static final String BOOK_NAME = "book_name";
|
||||||
|
public static final String ROUTER_BOOK = "hosts.txt";
|
||||||
|
public static final String PRIVATE_BOOK = "privatehosts.txt";
|
||||||
|
public static final String ADD_WIZARD_DATA = "add_wizard_data";
|
||||||
|
|
||||||
|
private static final int ADD_WIZARD_REQUEST = 1;
|
||||||
|
|
||||||
|
private static final int ROUTER_LOADER_ID = 1;
|
||||||
|
private static final int PRIVATE_LOADER_ID = 2;
|
||||||
|
|
||||||
|
private boolean mOnActivityCreated;
|
||||||
|
private RouterContextProvider mRouterContextProvider;
|
||||||
|
private OnAddressSelectedListener mCallback;
|
||||||
|
private AddressEntryAdapter mAdapter;
|
||||||
|
private String mBook;
|
||||||
|
private String mCurFilter;
|
||||||
|
|
||||||
|
// Set in onActivityResult()
|
||||||
|
private Intent mAddWizardData;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface OnAddressSelectedListener {
|
||||||
|
public void onAddressSelected(CharSequence host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mRouterContextProvider = (RouterContextProvider) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement RouterContextProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mCallback = (OnAddressSelectedListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement OnAddressSelectedListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
mAdapter = new AddressEntryAdapter(getActivity());
|
||||||
|
mBook = getArguments().getString(BOOK_NAME);
|
||||||
|
|
||||||
|
// Set adapter to null before setting the header
|
||||||
|
setListAdapter(null);
|
||||||
|
|
||||||
|
TextView v = new TextView(getActivity());
|
||||||
|
v.setTag("addressbook_header");
|
||||||
|
getListView().addHeaderView(v);
|
||||||
|
|
||||||
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
|
mOnActivityCreated = true;
|
||||||
|
if (getRouterContext() != null)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
else
|
||||||
|
setEmptyText(getResources().getString(
|
||||||
|
R.string.router_not_running));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRouterConnectionReady() {
|
||||||
|
// Show actions
|
||||||
|
if (mSearchAddressbook != null)
|
||||||
|
mSearchAddressbook.setVisible(true);
|
||||||
|
if (mAddToAddressbook != null)
|
||||||
|
mAddToAddressbook.setVisible(false);
|
||||||
|
|
||||||
|
if (mAddWizardData != null) {
|
||||||
|
// Save the new entry
|
||||||
|
Bundle entryData = mAddWizardData.getExtras().getBundle(ADD_WIZARD_DATA);
|
||||||
|
NamingService ns = NamingServiceUtil.getNamingService(getRouterContext(), mBook);
|
||||||
|
boolean success = NamingServiceUtil.addFromWizard(getActivity(), ns, entryData, false);
|
||||||
|
if (success) {
|
||||||
|
// Reload the list
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().restartLoader(PRIVATE_LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setEmptyText("No hosts in address book " + mBook);
|
||||||
|
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().initLoader(PRIVATE_BOOK.equals(mBook) ?
|
||||||
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||||
|
CharSequence host = ((TextView) view).getText();
|
||||||
|
mCallback.onAddressSelected(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuItem mSearchAddressbook;
|
||||||
|
private MenuItem mAddToAddressbook;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
|
||||||
|
|
||||||
|
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
|
||||||
|
mAddToAddressbook = menu.findItem(R.id.action_add_to_addressbook);
|
||||||
|
|
||||||
|
// Hide until needed
|
||||||
|
if (getRouterContext() == null) {
|
||||||
|
mSearchAddressbook.setVisible(false);
|
||||||
|
mAddToAddressbook.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow adding to private book
|
||||||
|
if (!PRIVATE_BOOK.equals(mBook)) {
|
||||||
|
mAddToAddressbook.setVisible(false);
|
||||||
|
mAddToAddressbook = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle presses on the action bar items
|
||||||
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_add_to_addressbook:
|
||||||
|
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
|
||||||
|
startActivityForResult(wi, ADD_WIZARD_REQUEST);
|
||||||
|
return true;
|
||||||
|
case R.id.action_reload_subscriptions:
|
||||||
|
Daemon.wakeup();
|
||||||
|
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
case R.id.action_addressbook_settings:
|
||||||
|
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
|
||||||
|
startActivity(si);
|
||||||
|
return true;
|
||||||
|
// TODO: Enable when Help page finished
|
||||||
|
//case R.id.action_addressbook_help:
|
||||||
|
// Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||||
|
// hi.putExtra(HelpActivity.REFERRER, "addressbook");
|
||||||
|
// startActivity(hi);
|
||||||
|
// return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == ADD_WIZARD_REQUEST &&
|
||||||
|
resultCode == Activity.RESULT_OK &&
|
||||||
|
PRIVATE_BOOK.equals(mBook)) {
|
||||||
|
mAddWizardData = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filterAddresses(String query) {
|
||||||
|
mCurFilter = !TextUtils.isEmpty(query) ? query : null;
|
||||||
|
if (getRouterContext() != null && mAdapter != null) {
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().restartLoader(PRIVATE_BOOK.equals(mBook) ?
|
||||||
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicated from I2PFragmentBase because this extends ListFragment
|
||||||
|
private RouterContext getRouterContext() {
|
||||||
|
return mRouterContextProvider.getRouterContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// I2PFragmentBase.RouterContextUser
|
||||||
|
|
||||||
|
public void onRouterBind() {
|
||||||
|
if (mOnActivityCreated)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoaderManager.LoaderCallbacks<List<AddressEntry>>
|
||||||
|
|
||||||
|
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new AddressEntryLoader(getActivity(),
|
||||||
|
getRouterContext(), mBook, mCurFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoadFinished(Loader<List<AddressEntry>> loader,
|
||||||
|
List<AddressEntry> data) {
|
||||||
|
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||||
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||||
|
mAdapter.setData(data);
|
||||||
|
|
||||||
|
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
|
||||||
|
if (mCurFilter != null)
|
||||||
|
v.setText(getActivity().getResources().getString(
|
||||||
|
R.string.addressbook_search_header,
|
||||||
|
data.size()));
|
||||||
|
else
|
||||||
|
v.setText("");
|
||||||
|
|
||||||
|
if (isResumed()) {
|
||||||
|
setListShown(true);
|
||||||
|
} else {
|
||||||
|
setListShownNoAnimation(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoaderReset(Loader<List<AddressEntry>> loader) {
|
||||||
|
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||||
|
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||||
|
mAdapter.setData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package net.i2p.android.router.addressbook;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.util.FileUtil;
|
||||||
|
|
||||||
|
public class AddressbookSettingsActivity extends Activity {
|
||||||
|
|
||||||
|
private EditText text_content_subscriptions;
|
||||||
|
private Button btn_save_subscriptions;
|
||||||
|
private String filename = "/addressbook/subscriptions.txt";
|
||||||
|
private File i2pDir;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_addressbook_settings);
|
||||||
|
text_content_subscriptions = (EditText) findViewById(R.id.subscriptions_content);
|
||||||
|
btn_save_subscriptions = (Button) findViewById(R.id.button_save_subscriptions);
|
||||||
|
init_actions();
|
||||||
|
i2pDir = new File(getFilesDir(), filename);
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.activity_addressbook_settings, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init_actions() {
|
||||||
|
btn_save_subscriptions.setOnClickListener(new View.OnClickListener() {
|
||||||
|
public void onClick(View view) {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
CharSequence text;
|
||||||
|
if (save()) {
|
||||||
|
text = "subscriptions.txt successfully saved!";
|
||||||
|
} else {
|
||||||
|
text = "there was a problem saving subscriptions.txt! Try fix permissions or reinstall i2p.";
|
||||||
|
}
|
||||||
|
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean load() {
|
||||||
|
String res = FileUtil.readTextFile(i2pDir.getAbsolutePath(), -1, true);
|
||||||
|
if (res.length() > 0) {
|
||||||
|
text_content_subscriptions.setText(res);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
CharSequence text = "Sorry, could not load subscriptions.txt!";
|
||||||
|
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean save() {
|
||||||
|
//
|
||||||
|
String content = text_content_subscriptions.getText().toString();
|
||||||
|
FileOutputStream out = null;
|
||||||
|
try {
|
||||||
|
out = new FileOutputStream(i2pDir);
|
||||||
|
byte[] contentInBytes = content.getBytes();
|
||||||
|
out.write(contentInBytes);
|
||||||
|
out.close();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (out != null) try {out.close(); } catch (IOException ioe) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package net.i2p.android.router.dialog;
|
||||||
|
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
public class AboutDialog extends DialogFragment {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||||
|
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||||
|
View view = li.inflate(R.layout.fragment_dialog_about, null);
|
||||||
|
|
||||||
|
final String currentVersion = Util.getOurVersion(getActivity());
|
||||||
|
TextView tv = (TextView)view.findViewById(R.id.about_version);
|
||||||
|
tv.setText(currentVersion);
|
||||||
|
|
||||||
|
tv = (TextView)view.findViewById(R.id.url_project);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
|
tv = (TextView)view.findViewById(R.id.url_android_bugs);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
|
tv = (TextView)view.findViewById(R.id.url_android_volunteer);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
|
tv = (TextView)view.findViewById(R.id.url_donate);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
|
|
||||||
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
|
b.setTitle(R.string.menu_about)
|
||||||
|
.setView(view);
|
||||||
|
return b.create();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
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.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;
|
||||||
|
|
||||||
|
public class FirstStartDialog extends DialogFragment {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||||
|
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||||
|
View view = li.inflate(R.layout.fragment_dialog_first_start, null);
|
||||||
|
|
||||||
|
TextView tv = (TextView)view.findViewById(R.id.url_faq);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||||
|
tv = (TextView)view.findViewById(R.id.url_irc_i2p);
|
||||||
|
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
|
||||||
|
|
||||||
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
|
b.setTitle(R.string.first_start_title)
|
||||||
|
.setView(view);
|
||||||
|
return b.create();
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,17 @@
|
|||||||
package net.i2p.android.router.activity;
|
package net.i2p.android.router.dialog;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.text.method.ScrollingMovementMethod;
|
import android.text.method.ScrollingMovementMethod;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@ -18,24 +19,28 @@ import net.i2p.android.router.util.Util;
|
|||||||
* 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 TextResourceActivity extends I2PActivityBase {
|
public class TextResourceDialog extends DialogFragment {
|
||||||
|
|
||||||
final static String TEXT_RESOURCE_ID = "text_resource_id";
|
public static final String TEXT_DIALOG_TITLE = "text_title";
|
||||||
|
public final static String TEXT_RESOURCE_ID = "text_resource_id";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState)
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
super.onCreate(savedInstanceState);
|
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
|
||||||
setContentView(R.layout.text_resource);
|
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
|
||||||
TextView tv = (TextView) findViewById(R.id.text_resource_text);
|
|
||||||
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
|
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
|
||||||
Intent intent = getIntent();
|
String title = getArguments().getString(TEXT_DIALOG_TITLE);
|
||||||
int id = intent.getIntExtra(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
if (title != null)
|
||||||
|
getDialog().setTitle(title);
|
||||||
|
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(this) + "\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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getResourceAsString(int id) {
|
private String getResourceAsString(int id) {
|
||||||
@ -45,7 +50,7 @@ public class TextResourceActivity extends I2PActivityBase {
|
|||||||
try {
|
try {
|
||||||
in = getResources().openRawResource(id);
|
in = getResources().openRawResource(id);
|
||||||
|
|
||||||
int read = 0;
|
int read;
|
||||||
while ( (read = in.read(buf)) != -1)
|
while ( (read = in.read(buf)) != -1)
|
||||||
out.write(buf, 0, read);
|
out.write(buf, 0, read);
|
||||||
|
|
@ -0,0 +1,89 @@
|
|||||||
|
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.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
|
||||||
|
public class VersionDialog extends DialogFragment {
|
||||||
|
public static final String DIALOG_TYPE = "dialog_type";
|
||||||
|
public static final int DIALOG_NEW_INSTALL = 0;
|
||||||
|
public static final int DIALOG_NEW_VERSION = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||||
|
final String currentVersion = Util.getOurVersion(getActivity());
|
||||||
|
Dialog rv = null;
|
||||||
|
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||||
|
int id = getArguments().getInt(DIALOG_TYPE);
|
||||||
|
switch(id) {
|
||||||
|
case DIALOG_NEW_INSTALL:
|
||||||
|
b.setMessage(R.string.welcome_new_install)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton("Dismiss", new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
|
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).setNegativeButton(R.string.label_release_notes, new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
|
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||||
|
dialog.dismiss();
|
||||||
|
TextResourceDialog f = new TextResourceDialog();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||||
|
f.setArguments(args);
|
||||||
|
getActivity().getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.main_fragment, f)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rv = b.create();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DIALOG_NEW_VERSION:
|
||||||
|
b.setMessage(getResources().getString(R.string.welcome_new_version) +
|
||||||
|
" " + currentVersion)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton("Dismiss", new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
|
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).setNegativeButton(R.string.label_release_notes, new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||||
|
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
|
||||||
|
dialog.dismiss();
|
||||||
|
TextResourceDialog f = new TextResourceDialog();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||||
|
f.setArguments(args);
|
||||||
|
getActivity().getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.main_fragment, f)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rv = b.create();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
131
app/src/main/java/net/i2p/android/router/log/LogActivity.java
Normal file
131
app/src/main/java/net/i2p/android/router/log/LogActivity.java
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package net.i2p.android.router.log;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.SpinnerAdapter;
|
||||||
|
|
||||||
|
public class LogActivity extends I2PActivityBase implements
|
||||||
|
LogFragment.OnEntrySelectedListener {
|
||||||
|
/**
|
||||||
|
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
private boolean mTwoPane;
|
||||||
|
|
||||||
|
private static final String SELECTED_LEVEL = "selected_level";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canUseTwoPanes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Set up action bar for drop-down list
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||||
|
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
|
||||||
|
if (findViewById(R.id.detail_fragment) != null) {
|
||||||
|
// The detail container view will be present only in the
|
||||||
|
// large-screen layouts (res/values-large and
|
||||||
|
// res/values-sw600dp). If this view is present, then the
|
||||||
|
// activity should be in two-pane mode.
|
||||||
|
mTwoPane = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
|
||||||
|
R.array.log_level_list, android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
|
||||||
|
ActionBar.OnNavigationListener mNavigationListener = new ActionBar.OnNavigationListener() {
|
||||||
|
String[] levels = getResources().getStringArray(R.array.log_level_list);
|
||||||
|
|
||||||
|
public boolean onNavigationItemSelected(int position, long itemId) {
|
||||||
|
String level = levels[position];
|
||||||
|
LogFragment f = LogFragment.newInstance(level);
|
||||||
|
// In two-pane mode, list items should be given the
|
||||||
|
// 'activated' state when touched.
|
||||||
|
if (mTwoPane)
|
||||||
|
f.setActivateOnItemClick(true);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.main_fragment, f, levels[position]).commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationListener);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
int selected = savedInstanceState.getInt(SELECTED_LEVEL);
|
||||||
|
actionBar.setSelectedNavigationItem(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.activity_base_actions, menu);
|
||||||
|
// Help menu not needed (yet), hide
|
||||||
|
// TODO: Unhide when Help finished
|
||||||
|
//menu.findItem(R.id.menu_help).setVisible(false);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_settings:
|
||||||
|
Intent intent = new Intent(LogActivity.this, SettingsActivity.class);
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
intent.setAction("net.i2p.android.router.PREFS_LOGGING");
|
||||||
|
} else { // TODO: Test if this works, fix if not
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("settings", "logging");
|
||||||
|
intent.putExtras(args);
|
||||||
|
}
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt(SELECTED_LEVEL,
|
||||||
|
getSupportActionBar().getSelectedNavigationIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogFragment.OnEntrySelectedListener
|
||||||
|
|
||||||
|
public void onEntrySelected(String entry) {
|
||||||
|
if (mTwoPane) {
|
||||||
|
// In two-pane mode, show the detail view in this activity by
|
||||||
|
// adding or replacing the detail fragment using a
|
||||||
|
// fragment transaction.
|
||||||
|
LogDetailFragment detailFrag = LogDetailFragment.newInstance(entry);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||||
|
} else {
|
||||||
|
// In single-pane mode, simply start the detail activity
|
||||||
|
// for the selected item ID.
|
||||||
|
Intent detailIntent = new Intent(this, LogDetailActivity.class);
|
||||||
|
detailIntent.putExtra(LogDetailFragment.LOG_ENTRY, entry);
|
||||||
|
startActivity(detailIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
app/src/main/java/net/i2p/android/router/log/LogAdapter.java
Normal file
24
app/src/main/java/net/i2p/android/router/log/LogAdapter.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package net.i2p.android.router.log;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
|
public class LogAdapter extends ArrayAdapter<String> {
|
||||||
|
|
||||||
|
public LogAdapter(Context context) {
|
||||||
|
super(context, R.layout.listitem_logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<String> entries) {
|
||||||
|
clear();
|
||||||
|
if (entries != null) {
|
||||||
|
for (String entry : entries) {
|
||||||
|
add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.i2p.android.router.log;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
public class LogDetailActivity extends I2PActivityBase {
|
||||||
|
LogDetailFragment mDetailFrag;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
String entry = getIntent().getStringExtra(LogDetailFragment.LOG_ENTRY);
|
||||||
|
mDetailFrag = LogDetailFragment.newInstance(entry);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, mDetailFrag).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package net.i2p.android.router.log;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.method.ScrollingMovementMethod;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class LogDetailFragment extends I2PFragmentBase {
|
||||||
|
public static final String LOG_ENTRY = "log_entry";
|
||||||
|
|
||||||
|
private String mEntry;
|
||||||
|
|
||||||
|
public static LogDetailFragment newInstance (String entry) {
|
||||||
|
LogDetailFragment f = new LogDetailFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(LOG_ENTRY, entry);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_log_entry, container, false);
|
||||||
|
|
||||||
|
mEntry = getArguments().getString(LOG_ENTRY);
|
||||||
|
TextView tv = (TextView) v.findViewById(R.id.log_entry);
|
||||||
|
tv.setMovementMethod(new ScrollingMovementMethod());
|
||||||
|
tv.setText(mEntry);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
188
app/src/main/java/net/i2p/android/router/log/LogFragment.java
Normal file
188
app/src/main/java/net/i2p/android/router/log/LogFragment.java
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package net.i2p.android.router.log;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import java.util.List;
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
public class LogFragment extends ListFragment implements
|
||||||
|
LoaderManager.LoaderCallbacks<List<String>> {
|
||||||
|
public static final String LOG_LEVEL = "log_level";
|
||||||
|
/**
|
||||||
|
* The serialization (saved instance state) Bundle key representing the
|
||||||
|
* activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||||
|
|
||||||
|
private static final int LEVEL_ERROR = 1;
|
||||||
|
private static final int LEVEL_ALL = 2;
|
||||||
|
|
||||||
|
OnEntrySelectedListener mEntrySelectedCallback;
|
||||||
|
private LogAdapter mAdapter;
|
||||||
|
private TextView mHeaderView;
|
||||||
|
private String mLogLevel;
|
||||||
|
/**
|
||||||
|
* The current activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||||
|
private boolean mActivateOnItemClick = false;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface OnEntrySelectedListener {
|
||||||
|
public void onEntrySelected(String entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogFragment newInstance(String level) {
|
||||||
|
LogFragment f = new LogFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(LOG_LEVEL, level);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement OnEntrySelectedListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
// Restore the previously serialized activated item position.
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||||
|
setActivatedPosition(savedInstanceState
|
||||||
|
.getInt(STATE_ACTIVATED_POSITION));
|
||||||
|
}
|
||||||
|
|
||||||
|
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||||
|
// give items the 'activated' state when touched.
|
||||||
|
getListView().setChoiceMode(
|
||||||
|
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
|
||||||
|
: ListView.CHOICE_MODE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
mAdapter = new LogAdapter(getActivity());
|
||||||
|
mLogLevel = getArguments().getString(LOG_LEVEL);
|
||||||
|
|
||||||
|
// set the header
|
||||||
|
mHeaderView = (TextView) getActivity().getLayoutInflater().inflate(R.layout.logs_header, null);
|
||||||
|
getListView().addHeaderView(mHeaderView, "", false);
|
||||||
|
|
||||||
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
|
I2PAppContext ctx = I2PAppContext.getCurrentContext();
|
||||||
|
if (ctx != null) {
|
||||||
|
setEmptyText("ERROR".equals(mLogLevel) ?
|
||||||
|
"No error messages" : "No messages");
|
||||||
|
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().initLoader("ERROR".equals(mLogLevel) ?
|
||||||
|
LEVEL_ERROR : LEVEL_ALL, null, this);
|
||||||
|
} else
|
||||||
|
setEmptyText(getResources().getString(
|
||||||
|
R.string.router_not_running));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||||
|
super.onListItemClick(parent, view, pos, id);
|
||||||
|
String entry = mAdapter.getItem(pos - 1);
|
||||||
|
mEntrySelectedCallback.onEntrySelected(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||||
|
// Serialize and persist the activated item position.
|
||||||
|
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||||
|
* given the 'activated' state when touched.
|
||||||
|
*/
|
||||||
|
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||||
|
mActivateOnItemClick = activateOnItemClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActivatedPosition(int position) {
|
||||||
|
if (position == ListView.INVALID_POSITION) {
|
||||||
|
getListView().setItemChecked(mActivatedPosition, false);
|
||||||
|
} else {
|
||||||
|
getListView().setItemChecked(position, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mActivatedPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** fixme plurals */
|
||||||
|
private static String getHeader(int sz, boolean errorsOnly) {
|
||||||
|
if (errorsOnly) {
|
||||||
|
if (sz == 0)
|
||||||
|
return "No error messages";
|
||||||
|
if (sz == 1)
|
||||||
|
return "1 error message";
|
||||||
|
return sz + " error messages, newest first";
|
||||||
|
}
|
||||||
|
if (sz == 0)
|
||||||
|
return "No messages";
|
||||||
|
if (sz == 1)
|
||||||
|
return "1 message";
|
||||||
|
return sz + " messages, newest first";
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoaderManager.LoaderCallbacks<List<String>>
|
||||||
|
|
||||||
|
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new LogLoader(getActivity(),
|
||||||
|
I2PAppContext.getCurrentContext(), mLogLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoadFinished(Loader<List<String>> loader,
|
||||||
|
List<String> data) {
|
||||||
|
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
|
||||||
|
LEVEL_ERROR : LEVEL_ALL)) {
|
||||||
|
mAdapter.setData(data);
|
||||||
|
String header = getHeader(data.size(), ("ERROR".equals(mLogLevel)));
|
||||||
|
mHeaderView.setText(header);
|
||||||
|
|
||||||
|
if (isResumed()) {
|
||||||
|
setListShown(true);
|
||||||
|
} else {
|
||||||
|
setListShownNoAnimation(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoaderReset(Loader<List<String>> loader) {
|
||||||
|
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
|
||||||
|
LEVEL_ERROR : LEVEL_ALL)) {
|
||||||
|
mAdapter.setData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
app/src/main/java/net/i2p/android/router/log/LogLoader.java
Normal file
132
app/src/main/java/net/i2p/android/router/log/LogLoader.java
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package net.i2p.android.router.log;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
public class LogLoader extends AsyncTaskLoader<List<String>> {
|
||||||
|
private I2PAppContext mCtx;
|
||||||
|
private String mLogLevel;
|
||||||
|
private List<String> mData;
|
||||||
|
|
||||||
|
private static final int MAX_LOG_LENGTH = 250;
|
||||||
|
|
||||||
|
public LogLoader(Context context, I2PAppContext ctx, String logLevel) {
|
||||||
|
super(context);
|
||||||
|
mCtx = ctx;
|
||||||
|
mLogLevel = logLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> loadInBackground() {
|
||||||
|
List<String> msgs;
|
||||||
|
if ("ERROR".equals(mLogLevel)) {
|
||||||
|
msgs = mCtx.logManager().getBuffer().getMostRecentCriticalMessages();
|
||||||
|
} else {
|
||||||
|
msgs = mCtx.logManager().getBuffer().getMostRecentMessages();
|
||||||
|
}
|
||||||
|
int sz = msgs.size();
|
||||||
|
if (sz > 1)
|
||||||
|
Collections.reverse(msgs);
|
||||||
|
if (sz > 0 && mData != null) {
|
||||||
|
String oldNewest = mData.size() > 0 ? mData.get(0) : null;
|
||||||
|
for (int i = 0; i < sz; i++) {
|
||||||
|
String newItem = msgs.get(i);
|
||||||
|
if (newItem.equals(oldNewest))
|
||||||
|
break;
|
||||||
|
mData.add(i, newItem);
|
||||||
|
}
|
||||||
|
int newSz = mData.size();
|
||||||
|
for (int i = newSz - 1; i > MAX_LOG_LENGTH; i--) {
|
||||||
|
mData.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(List<String> data) {
|
||||||
|
if (isReset()) {
|
||||||
|
// The Loader has been reset; ignore the result and invalidate the data.
|
||||||
|
if (data != null) {
|
||||||
|
releaseResources(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||||
|
// We must protect it until the new data has been delivered.
|
||||||
|
List<String> oldData = mData;
|
||||||
|
mData = data;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
// If the Loader is in a started state, have the superclass deliver the
|
||||||
|
// results to the client.
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the old data as we don't need it any more.
|
||||||
|
if (oldData != null && oldData != data) {
|
||||||
|
releaseResources(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mData != null) {
|
||||||
|
// Deliver any previously loaded data immediately.
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeContentChanged() || mData == null) {
|
||||||
|
// When the observer detects a change, it should call onContentChanged()
|
||||||
|
// on the Loader, which will cause the next call to takeContentChanged()
|
||||||
|
// to return true. If this is ever the case (or if the current data is
|
||||||
|
// null), we force a new load.
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||||
|
// current load (if there is one).
|
||||||
|
cancelLoad();
|
||||||
|
|
||||||
|
// Note that we leave the observer as is. Loaders in a stopped state
|
||||||
|
// should still monitor the data source for changes so that the Loader
|
||||||
|
// will know to force a new load if it is ever started again.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
// Ensure the loader has been stopped.
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
// At this point we can release the resources associated with 'mData'.
|
||||||
|
if (mData != null) {
|
||||||
|
releaseResources(mData);
|
||||||
|
mData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(List<String> data) {
|
||||||
|
// Attempt to cancel the current asynchronous load.
|
||||||
|
super.onCanceled(data);
|
||||||
|
|
||||||
|
// The load has been canceled, so we should release the resources
|
||||||
|
// associated with 'data'.
|
||||||
|
releaseResources(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseResources(List<String> data) {
|
||||||
|
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||||
|
// would close it in this method. All resources associated with the Loader
|
||||||
|
// should be released here.
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.app.ActionBar.Tab;
|
||||||
|
|
||||||
|
public class NetDbActivity extends I2PActivityBase implements
|
||||||
|
NetDbListFragment.OnEntrySelectedListener {
|
||||||
|
/**
|
||||||
|
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||||
|
* device.
|
||||||
|
*/
|
||||||
|
private boolean mTwoPane;
|
||||||
|
|
||||||
|
private static final String SELECTED_TAB = "selected_tab";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canUseTwoPanes() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Set up action bar for tabs
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||||
|
|
||||||
|
// Statistics tab
|
||||||
|
NetDbSummaryPagerFragment sf = new NetDbSummaryPagerFragment();
|
||||||
|
actionBar.addTab(
|
||||||
|
actionBar.newTab()
|
||||||
|
.setText("Statistics")
|
||||||
|
.setTabListener(new NetDbSummaryPagerTabListener(sf)));
|
||||||
|
|
||||||
|
// Routers tab
|
||||||
|
NetDbListFragment rf = new NetDbListFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, true);
|
||||||
|
rf.setArguments(args);
|
||||||
|
actionBar.addTab(
|
||||||
|
actionBar.newTab()
|
||||||
|
.setText("Routers")
|
||||||
|
.setTabListener(new TabListener(rf)));
|
||||||
|
|
||||||
|
// LeaseSets tab
|
||||||
|
NetDbListFragment lf = new NetDbListFragment();
|
||||||
|
args = new Bundle();
|
||||||
|
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, false);
|
||||||
|
lf.setArguments(args);
|
||||||
|
actionBar.addTab(
|
||||||
|
actionBar.newTab()
|
||||||
|
.setText("LeaseSets")
|
||||||
|
.setTabListener(new TabListener(lf)));
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
int selected = savedInstanceState.getInt(SELECTED_TAB);
|
||||||
|
actionBar.setSelectedNavigationItem(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// In two-pane mode, list items should be given the
|
||||||
|
// 'activated' state when touched.
|
||||||
|
rf.setActivateOnItemClick(true);
|
||||||
|
lf.setActivateOnItemClick(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt(SELECTED_TAB,
|
||||||
|
getSupportActionBar().getSelectedNavigationIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NetDbSummaryPagerTabListener extends TabListener {
|
||||||
|
public NetDbSummaryPagerTabListener(Fragment fragment) {
|
||||||
|
super(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(Tab tab, FragmentTransaction ft) {
|
||||||
|
/**
|
||||||
|
* This is a work-around for Issue 42601
|
||||||
|
* https://code.google.com/p/android/issues/detail?id=42601
|
||||||
|
*
|
||||||
|
* The method getChildFragmentManager() does not clear up
|
||||||
|
* when the Fragment is detached.
|
||||||
|
*/
|
||||||
|
mFragment = new NetDbSummaryPagerFragment();
|
||||||
|
super.onTabSelected(tab, ft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetDbListFragment.OnEntrySelectedListener
|
||||||
|
|
||||||
|
public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
|
||||||
|
if (mTwoPane) {
|
||||||
|
// In two-pane mode, show the detail view in this activity by
|
||||||
|
// adding or replacing the detail fragment using a
|
||||||
|
// fragment transaction.
|
||||||
|
NetDbDetailFragment detailFrag = NetDbDetailFragment.newInstance(
|
||||||
|
isRouterInfo, entryHash);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||||
|
|
||||||
|
// If we are coming from a LS to a RI, change the tab
|
||||||
|
int currentTab = getSupportActionBar().getSelectedNavigationIndex();
|
||||||
|
if (isRouterInfo && currentTab !=1)
|
||||||
|
getSupportActionBar().setSelectedNavigationItem(1);
|
||||||
|
} else {
|
||||||
|
// In single-pane mode, simply start the detail activity
|
||||||
|
// for the selected item ID.
|
||||||
|
Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
|
||||||
|
detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
|
||||||
|
detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
|
||||||
|
entryHash.toBase64());
|
||||||
|
startActivity(detailIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class NetDbDetailActivity extends I2PActivityBase implements
|
||||||
|
NetDbListFragment.OnEntrySelectedListener {
|
||||||
|
NetDbDetailFragment mDetailFrag;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
boolean isRI = getIntent().getBooleanExtra(NetDbDetailFragment.IS_RI, true);
|
||||||
|
Hash hash = new Hash();
|
||||||
|
try {
|
||||||
|
hash.fromBase64(getIntent().getStringExtra(NetDbDetailFragment.ENTRY_HASH));
|
||||||
|
mDetailFrag = NetDbDetailFragment.newInstance(isRI, hash);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, mDetailFrag).commit();
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
Util.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetDbListFragment.OnEntrySelectedListener
|
||||||
|
|
||||||
|
public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
|
||||||
|
// Start the detail activity for the selected item ID.
|
||||||
|
Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
|
||||||
|
detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
|
||||||
|
detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
|
||||||
|
entryHash.toBase64());
|
||||||
|
startActivity(detailIntent);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,246 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TableLayout;
|
||||||
|
import android.widget.TableRow;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.netdb.NetDbListFragment.OnEntrySelectedListener;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import net.i2p.data.Lease;
|
||||||
|
import net.i2p.data.LeaseSet;
|
||||||
|
import net.i2p.data.RouterAddress;
|
||||||
|
import net.i2p.data.RouterInfo;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class NetDbDetailFragment extends I2PFragmentBase {
|
||||||
|
public static final String IS_RI = "is_routerinfo";
|
||||||
|
public static final String ENTRY_HASH = "entry_hash";
|
||||||
|
|
||||||
|
OnEntrySelectedListener mEntrySelectedCallback;
|
||||||
|
private NetDbEntry mEntry;
|
||||||
|
|
||||||
|
public static NetDbDetailFragment newInstance(boolean isRI, Hash hash) {
|
||||||
|
NetDbDetailFragment f = new NetDbDetailFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(IS_RI, isRI);
|
||||||
|
args.putString(ENTRY_HASH, hash.toBase64());
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement OnEntrySelectedListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v;
|
||||||
|
if (getArguments().getBoolean(IS_RI)) {
|
||||||
|
v = inflater.inflate(R.layout.fragment_netdb_router_detail, container, false);
|
||||||
|
} else {
|
||||||
|
v = inflater.inflate(R.layout.fragment_netdb_leaseset_detail, container, false);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRouterConnectionReady() {
|
||||||
|
if (getRouterContext() != null && mEntry == null)
|
||||||
|
loadEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadEntry() {
|
||||||
|
if (getNetDb().isInitialized()) {
|
||||||
|
Hash hash = new Hash();
|
||||||
|
try {
|
||||||
|
hash.fromBase64(getArguments().getString(ENTRY_HASH));
|
||||||
|
if (getArguments().getBoolean(IS_RI)) {
|
||||||
|
// Load RouterInfo
|
||||||
|
RouterInfo ri = getNetDb().lookupRouterInfoLocally(hash);
|
||||||
|
if (ri != null)
|
||||||
|
loadRouterInfo(ri);
|
||||||
|
// TODO: Handle null case in UI
|
||||||
|
} else {
|
||||||
|
// Load LeaseSet
|
||||||
|
LeaseSet ls = getNetDb().lookupLeaseSetLocally(hash);
|
||||||
|
if (ls != null)
|
||||||
|
loadLeaseSet(ls);
|
||||||
|
// TODO: Handle null case in UI
|
||||||
|
}
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
Util.e(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRouterInfo(RouterInfo ri) {
|
||||||
|
mEntry = NetDbEntry.fromRouterInfo(getRouterContext(), ri);
|
||||||
|
|
||||||
|
if (mEntry.isUs())
|
||||||
|
getActivity().setTitle("Our info");
|
||||||
|
else
|
||||||
|
getActivity().setTitle("Peer info");
|
||||||
|
|
||||||
|
TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
|
||||||
|
entryHash.setText(mEntry.getHash().toBase64());
|
||||||
|
|
||||||
|
if (mEntry.isUs() && getRouter().isHidden()) {
|
||||||
|
TextView pubLabel = (TextView) getView().findViewById(R.id.label_ri_published);
|
||||||
|
pubLabel.setText("Hidden, Updated:");
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView published = (TextView) getView().findViewById(R.id.ri_published);
|
||||||
|
long age = getRouterContext().clock().now() - ri.getPublished();
|
||||||
|
if (age > 0) {
|
||||||
|
published.setText(DataHelper.formatDuration(age) + " ago");
|
||||||
|
} else {
|
||||||
|
// shouldn't happen
|
||||||
|
published.setText(DataHelper.formatDuration(0-age) + " ago???");
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearLayout addresses = (LinearLayout) getView().findViewById(R.id.ri_addresses);
|
||||||
|
for (RouterAddress addr : ri.getAddresses()) {
|
||||||
|
addAddress(addresses, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableLayout stats = (TableLayout) getView().findViewById(R.id.ri_stats);
|
||||||
|
Map<Object, Object> p = ri.getOptionsMap();
|
||||||
|
for (Map.Entry<Object,Object> e : p.entrySet()) {
|
||||||
|
String key = (String)e.getKey();
|
||||||
|
String val = (String)e.getValue();
|
||||||
|
addTableRow(stats, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAddress(LinearLayout addresses, RouterAddress addr) {
|
||||||
|
TableLayout table = new TableLayout(getActivity());
|
||||||
|
|
||||||
|
String style = addr.getTransportStyle();
|
||||||
|
addTableRow(table, "Style", style);
|
||||||
|
|
||||||
|
int cost = addr.getCost();
|
||||||
|
if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
|
||||||
|
addTableRow(table, "cost", ""+cost);
|
||||||
|
|
||||||
|
Map<Object, Object> p = addr.getOptionsMap();
|
||||||
|
for (Map.Entry<Object,Object> e : p.entrySet()) {
|
||||||
|
String key = (String)e.getKey();
|
||||||
|
String val = (String)e.getValue();
|
||||||
|
addTableRow(table, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses.addView(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLeaseSet(LeaseSet ls) {
|
||||||
|
mEntry = NetDbEntry.fromLeaseSet(getRouterContext(), ls);
|
||||||
|
|
||||||
|
getActivity().setTitle("LeaseSet");
|
||||||
|
|
||||||
|
TextView nickname = (TextView) getView().findViewById(R.id.ls_nickname);
|
||||||
|
nickname.setText(mEntry.getNickname());
|
||||||
|
|
||||||
|
TextView type = (TextView) getView().findViewById(R.id.ls_type);
|
||||||
|
if (mEntry.isLocal()) {
|
||||||
|
if (mEntry.isUnpublished())
|
||||||
|
type.setText("Local Unpublished Destination");
|
||||||
|
else
|
||||||
|
type.setText("Local Destination");
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
|
||||||
|
entryHash.setText(mEntry.getHash().toBase64());
|
||||||
|
|
||||||
|
TextView expiry = (TextView) getView().findViewById(R.id.ls_expiry);
|
||||||
|
long exp = ls.getLatestLeaseDate() - getRouterContext().clock().now();
|
||||||
|
if (exp > 0) {
|
||||||
|
expiry.setText(DataHelper.formatDuration(exp));
|
||||||
|
} else {
|
||||||
|
TextView expiryLabel = (TextView) getView().findViewById(R.id.label_ls_expiry);
|
||||||
|
expiryLabel.setText("Expired:");
|
||||||
|
expiry.setText(DataHelper.formatDuration(exp) + " ago");
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearLayout leases = (LinearLayout) getView().findViewById(R.id.ls_leases);
|
||||||
|
for (int i = 0; i < ls.getLeaseCount(); i++) {
|
||||||
|
Lease lease = ls.getLease(i);
|
||||||
|
addLease(leases, lease, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLease(LinearLayout leases, Lease lease, int i) {
|
||||||
|
TableLayout table = new TableLayout(getActivity());
|
||||||
|
|
||||||
|
addTableRow(table, "Lease", ""+(i+1));
|
||||||
|
|
||||||
|
TableRow gateway = new TableRow(getActivity());
|
||||||
|
gateway.setPadding(10, 0, 0, 0);
|
||||||
|
|
||||||
|
TextView gatewayLabel = new TextView(getActivity());
|
||||||
|
gatewayLabel.setText("Gateway");
|
||||||
|
|
||||||
|
Button gatewayButton = new Button(getActivity());
|
||||||
|
gatewayButton.setText(lease.getGateway().toBase64().substring(0, 4));
|
||||||
|
final Hash gatewayHash = lease.getGateway();
|
||||||
|
gatewayButton.setOnClickListener(new OnClickListener() {
|
||||||
|
public void onClick(View view) {
|
||||||
|
mEntrySelectedCallback.onEntrySelected(
|
||||||
|
true, gatewayHash);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gateway.addView(gatewayLabel);
|
||||||
|
gateway.addView(gatewayButton);
|
||||||
|
|
||||||
|
table.addView(gateway);
|
||||||
|
|
||||||
|
addTableRow(table, "Tunnel", ""+lease.getTunnelId().getTunnelId());
|
||||||
|
|
||||||
|
leases.addView(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTableRow(TableLayout table, String key, String val) {
|
||||||
|
TableRow row;
|
||||||
|
TextView tl1, tl2;
|
||||||
|
|
||||||
|
row = new TableRow(getActivity());
|
||||||
|
row.setPadding(10, 0, 0, 0);
|
||||||
|
|
||||||
|
tl1 = new TextView(getActivity());
|
||||||
|
tl2 = new TextView(getActivity());
|
||||||
|
|
||||||
|
tl1.setText(key);
|
||||||
|
tl2.setText(val);
|
||||||
|
|
||||||
|
row.addView(tl1);
|
||||||
|
row.addView(tl2);
|
||||||
|
|
||||||
|
table.addView(row);
|
||||||
|
}
|
||||||
|
}
|
124
app/src/main/java/net/i2p/android/router/netdb/NetDbEntry.java
Normal file
124
app/src/main/java/net/i2p/android/router/netdb/NetDbEntry.java
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.data.DatabaseEntry;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import net.i2p.data.LeaseSet;
|
||||||
|
import net.i2p.data.RouterInfo;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.TunnelPoolSettings;
|
||||||
|
|
||||||
|
public class NetDbEntry {
|
||||||
|
private final boolean mIsRI;
|
||||||
|
private final DatabaseEntry mEntry;
|
||||||
|
|
||||||
|
private final boolean mIsUs;
|
||||||
|
private final String mCountry;
|
||||||
|
|
||||||
|
private final String mNick;
|
||||||
|
private final boolean mLocal;
|
||||||
|
private final boolean mUnpublished;
|
||||||
|
|
||||||
|
public static NetDbEntry fromRouterInfo(RouterContext ctx, RouterInfo ri) {
|
||||||
|
Hash us = ctx.routerHash();
|
||||||
|
boolean isUs = ri.getHash().equals(us);
|
||||||
|
// XXX Disabled, no GeoIP file
|
||||||
|
String country = "";//ctx.commSystem().getCountry(ri.getIdentity().getHash());
|
||||||
|
return new NetDbEntry(ri, isUs, country);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NetDbEntry fromLeaseSet(RouterContext ctx, LeaseSet ls) {
|
||||||
|
String nick;
|
||||||
|
boolean local = false;
|
||||||
|
boolean unpublished = false;
|
||||||
|
Destination dest = ls.getDestination();
|
||||||
|
Hash key = dest.calculateHash();
|
||||||
|
if (ctx.clientManager().isLocal(dest)) {
|
||||||
|
local = true;
|
||||||
|
if (! ctx.clientManager().shouldPublishLeaseSet(key))
|
||||||
|
unpublished = true;
|
||||||
|
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(key);
|
||||||
|
if (in != null && in.getDestinationNickname() != null)
|
||||||
|
nick = in.getDestinationNickname();
|
||||||
|
else
|
||||||
|
nick = dest.toBase64().substring(0, 6);
|
||||||
|
} else {
|
||||||
|
String host = ctx.namingService().reverseLookup(dest);
|
||||||
|
if (host != null)
|
||||||
|
nick = host;
|
||||||
|
else
|
||||||
|
nick = dest.toBase64().substring(0, 6);
|
||||||
|
}
|
||||||
|
return new NetDbEntry(ls, nick, local, unpublished);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetDbEntry(RouterInfo ri,
|
||||||
|
boolean isUs, String country) {
|
||||||
|
mIsRI = true;
|
||||||
|
mEntry = ri;
|
||||||
|
|
||||||
|
mIsUs = isUs;
|
||||||
|
mCountry = country;
|
||||||
|
|
||||||
|
mNick = "";
|
||||||
|
mLocal = mUnpublished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetDbEntry(LeaseSet ls,
|
||||||
|
String nick, boolean local, boolean unpublished) {
|
||||||
|
mIsRI = false;
|
||||||
|
mEntry = ls;
|
||||||
|
|
||||||
|
mNick = nick;
|
||||||
|
mLocal = local;
|
||||||
|
mUnpublished = unpublished;
|
||||||
|
|
||||||
|
mIsUs = false;
|
||||||
|
mCountry = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRouterInfo() {
|
||||||
|
return mIsRI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// General methods
|
||||||
|
|
||||||
|
public Hash getHash() {
|
||||||
|
return mEntry.getHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterInfo-specific methods
|
||||||
|
|
||||||
|
public boolean isUs() {
|
||||||
|
return mIsUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCountryIcon() {
|
||||||
|
// http://daniel-codes.blogspot.com/2009/12/dynamically-retrieving-resources-in.html
|
||||||
|
try {
|
||||||
|
Class<R.drawable> res = R.drawable.class;
|
||||||
|
Field field = res.getField("flag_" + mCountry);
|
||||||
|
return field.getInt(null);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaseSet-specific methods
|
||||||
|
|
||||||
|
public String getNickname() {
|
||||||
|
return mNick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return mLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUnpublished() {
|
||||||
|
return mUnpublished;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class NetDbEntryAdapter extends ArrayAdapter<NetDbEntry> {
|
||||||
|
private final LayoutInflater mInflater;
|
||||||
|
|
||||||
|
public NetDbEntryAdapter(Context context) {
|
||||||
|
super(context, android.R.layout.simple_list_item_2);
|
||||||
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<NetDbEntry> entries) {
|
||||||
|
clear();
|
||||||
|
if (entries != null) {
|
||||||
|
for (NetDbEntry entry : entries) {
|
||||||
|
add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View v;
|
||||||
|
NetDbEntry entry = getItem(position);
|
||||||
|
|
||||||
|
if (entry.isRouterInfo()) {
|
||||||
|
v = mInflater.inflate(R.layout.listitem_routerinfo, parent, false);
|
||||||
|
|
||||||
|
int countryIcon = entry.getCountryIcon();
|
||||||
|
if (countryIcon > 0) {
|
||||||
|
ImageView country = (ImageView) v.findViewById(R.id.ri_country);
|
||||||
|
country.setImageDrawable(getContext().getResources()
|
||||||
|
.getDrawable(countryIcon));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = mInflater.inflate(R.layout.listitem_leaseset, parent, false);
|
||||||
|
|
||||||
|
TextView nickname = (TextView) v.findViewById(R.id.ls_nickname);
|
||||||
|
nickname.setText(entry.getNickname());
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView hash = (TextView) v.findViewById(R.id.dbentry_hash);
|
||||||
|
hash.setText(entry.getHash().toBase64());
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.data.LeaseSet;
|
||||||
|
import net.i2p.data.RouterInfo;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
|
||||||
|
private RouterContext mRContext;
|
||||||
|
private boolean mRouters;
|
||||||
|
private List<NetDbEntry> mData;
|
||||||
|
|
||||||
|
public NetDbEntryLoader(Context context, RouterContext rContext, boolean routers) {
|
||||||
|
super(context);
|
||||||
|
mRContext = rContext;
|
||||||
|
mRouters = routers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RouterInfoComparator implements Comparator<RouterInfo> {
|
||||||
|
public int compare(RouterInfo l, RouterInfo r) {
|
||||||
|
return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LeaseSetComparator implements Comparator<LeaseSet> {
|
||||||
|
public int compare(LeaseSet l, LeaseSet r) {
|
||||||
|
Destination dl = l.getDestination();
|
||||||
|
Destination dr = r.getDestination();
|
||||||
|
boolean locall = mRContext.clientManager().isLocal(dl);
|
||||||
|
boolean localr = mRContext.clientManager().isLocal(dr);
|
||||||
|
if (locall && !localr) return -1;
|
||||||
|
if (localr && !locall) return 1;
|
||||||
|
return dl.calculateHash().toBase64().compareTo(dr.calculateHash().toBase64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NetDbEntry> loadInBackground() {
|
||||||
|
List<NetDbEntry> ret = new ArrayList<NetDbEntry>();
|
||||||
|
if (mRContext.netDb().isInitialized()) {
|
||||||
|
if (mRouters) {
|
||||||
|
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
|
||||||
|
routers.addAll(mRContext.netDb().getRouters());
|
||||||
|
for (RouterInfo ri : routers) {
|
||||||
|
NetDbEntry entry = NetDbEntry.fromRouterInfo(mRContext, ri);
|
||||||
|
ret.add(entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Set<LeaseSet> leases = new TreeSet<LeaseSet>(new LeaseSetComparator());
|
||||||
|
leases.addAll(mRContext.netDb().getLeases());
|
||||||
|
for (LeaseSet ls : leases) {
|
||||||
|
NetDbEntry entry = NetDbEntry.fromLeaseSet(mRContext, ls);
|
||||||
|
ret.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(List<NetDbEntry> data) {
|
||||||
|
if (isReset()) {
|
||||||
|
// The Loader has been reset; ignore the result and invalidate the data.
|
||||||
|
if (data != null) {
|
||||||
|
releaseResources(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||||
|
// We must protect it until the new data has been delivered.
|
||||||
|
List<NetDbEntry> oldData = mData;
|
||||||
|
mData = data;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
// If the Loader is in a started state, have the superclass deliver the
|
||||||
|
// results to the client.
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the old data as we don't need it any more.
|
||||||
|
if (oldData != null && oldData != data) {
|
||||||
|
releaseResources(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mData != null) {
|
||||||
|
// Deliver any previously loaded data immediately.
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeContentChanged() || mData == null) {
|
||||||
|
// When the observer detects a change, it should call onContentChanged()
|
||||||
|
// on the Loader, which will cause the next call to takeContentChanged()
|
||||||
|
// to return true. If this is ever the case (or if the current data is
|
||||||
|
// null), we force a new load.
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||||
|
// current load (if there is one).
|
||||||
|
cancelLoad();
|
||||||
|
|
||||||
|
// Note that we leave the observer as is. Loaders in a stopped state
|
||||||
|
// should still monitor the data source for changes so that the Loader
|
||||||
|
// will know to force a new load if it is ever started again.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
// Ensure the loader has been stopped.
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
// At this point we can release the resources associated with 'mData'.
|
||||||
|
if (mData != null) {
|
||||||
|
releaseResources(mData);
|
||||||
|
mData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(List<NetDbEntry> data) {
|
||||||
|
// Attempt to cancel the current asynchronous load.
|
||||||
|
super.onCanceled(data);
|
||||||
|
|
||||||
|
// The load has been canceled, so we should release the resources
|
||||||
|
// associated with 'data'.
|
||||||
|
releaseResources(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseResources(List<NetDbEntry> data) {
|
||||||
|
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||||
|
// would close it in this method. All resources associated with the Loader
|
||||||
|
// should be released here.
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NetDbListFragment extends ListFragment implements
|
||||||
|
I2PFragmentBase.RouterContextUser,
|
||||||
|
LoaderManager.LoaderCallbacks<List<NetDbEntry>> {
|
||||||
|
public static final String SHOW_ROUTERS = "show_routers";
|
||||||
|
|
||||||
|
private static final int ROUTER_LOADER_ID = 1;
|
||||||
|
private static final int LEASESET_LOADER_ID = 2;
|
||||||
|
/**
|
||||||
|
* The serialization (saved instance state) Bundle key representing the
|
||||||
|
* activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||||
|
|
||||||
|
private boolean mOnActivityCreated;
|
||||||
|
private RouterContextProvider mRouterContextProvider;
|
||||||
|
private OnEntrySelectedListener mEntrySelectedCallback;
|
||||||
|
private NetDbEntryAdapter mAdapter;
|
||||||
|
private boolean mRouters;
|
||||||
|
/**
|
||||||
|
* The current activated item position. Only used on tablets.
|
||||||
|
*/
|
||||||
|
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||||
|
private boolean mActivateOnItemClick = false;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface OnEntrySelectedListener {
|
||||||
|
public void onEntrySelected(boolean isRouterInfo, Hash entryHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mRouterContextProvider = (RouterContextProvider) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement RouterContextProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This makes sure that the container activity has implemented
|
||||||
|
// the callback interface. If not, it throws an exception
|
||||||
|
try {
|
||||||
|
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement OnEntrySelectedListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
// Restore the previously serialized activated item position.
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||||
|
setActivatedPosition(savedInstanceState
|
||||||
|
.getInt(STATE_ACTIVATED_POSITION));
|
||||||
|
}
|
||||||
|
|
||||||
|
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||||
|
// give items the 'activated' state when touched.
|
||||||
|
getListView().setChoiceMode(
|
||||||
|
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
|
||||||
|
: ListView.CHOICE_MODE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
mAdapter = new NetDbEntryAdapter(getActivity());
|
||||||
|
mRouters = getArguments().getBoolean(SHOW_ROUTERS);
|
||||||
|
|
||||||
|
setListAdapter(mAdapter);
|
||||||
|
|
||||||
|
mOnActivityCreated = true;
|
||||||
|
if (getRouterContext() != null)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
else
|
||||||
|
setEmptyText(getResources().getString(
|
||||||
|
R.string.router_not_running));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRouterConnectionReady() {
|
||||||
|
setEmptyText(getResources().getString((mRouters ?
|
||||||
|
R.string.netdb_routers_empty :
|
||||||
|
R.string.netdb_leases_empty)));
|
||||||
|
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().initLoader(mRouters ? ROUTER_LOADER_ID
|
||||||
|
: LEASESET_LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||||
|
super.onListItemClick(parent, view, pos, id);
|
||||||
|
NetDbEntry entry = mAdapter.getItem(pos);
|
||||||
|
mEntrySelectedCallback.onEntrySelected(
|
||||||
|
entry.isRouterInfo(), entry.getHash());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||||
|
// Serialize and persist the activated item position.
|
||||||
|
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_netdb_list_actions, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle presses on the action bar items
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_refresh:
|
||||||
|
if (getRouterContext() != null) {
|
||||||
|
setListShown(false);
|
||||||
|
getLoaderManager().restartLoader(mRouters ? ROUTER_LOADER_ID
|
||||||
|
: LEASESET_LOADER_ID, null, this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||||
|
* given the 'activated' state when touched.
|
||||||
|
*/
|
||||||
|
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||||
|
mActivateOnItemClick = activateOnItemClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActivatedPosition(int position) {
|
||||||
|
if (position == ListView.INVALID_POSITION) {
|
||||||
|
getListView().setItemChecked(mActivatedPosition, false);
|
||||||
|
} else {
|
||||||
|
getListView().setItemChecked(position, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mActivatedPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicated from I2PFragmentBase because this extends ListFragment
|
||||||
|
private RouterContext getRouterContext() {
|
||||||
|
return mRouterContextProvider.getRouterContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// I2PFragmentBase.RouterContextUser
|
||||||
|
|
||||||
|
public void onRouterBind() {
|
||||||
|
if (mOnActivityCreated)
|
||||||
|
onRouterConnectionReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoaderManager.LoaderCallbacks<List<NetDbEntry>>
|
||||||
|
|
||||||
|
public Loader<List<NetDbEntry>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new NetDbEntryLoader(getActivity(),
|
||||||
|
getRouterContext(), mRouters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoadFinished(Loader<List<NetDbEntry>> loader,
|
||||||
|
List<NetDbEntry> data) {
|
||||||
|
if (loader.getId() == (mRouters ?
|
||||||
|
ROUTER_LOADER_ID : LEASESET_LOADER_ID)) {
|
||||||
|
mAdapter.setData(data);
|
||||||
|
|
||||||
|
if (isResumed()) {
|
||||||
|
setListShown(true);
|
||||||
|
} else {
|
||||||
|
setListShownNoAnimation(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoaderReset(Loader<List<NetDbEntry>> loader) {
|
||||||
|
if (loader.getId() == (mRouters ?
|
||||||
|
ROUTER_LOADER_ID : LEASESET_LOADER_ID)) {
|
||||||
|
mAdapter.setData(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import net.i2p.data.Hash;
|
||||||
|
import net.i2p.data.RouterAddress;
|
||||||
|
import net.i2p.data.RouterInfo;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.util.ObjectCounter;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
|
public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>>> {
|
||||||
|
private RouterContext mRContext;
|
||||||
|
private List<ObjectCounter<String>> mData;
|
||||||
|
|
||||||
|
public NetDbStatsLoader(Context context, RouterContext rContext) {
|
||||||
|
super(context);
|
||||||
|
mRContext = rContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RouterInfoComparator implements Comparator<RouterInfo> {
|
||||||
|
public int compare(RouterInfo l, RouterInfo r) {
|
||||||
|
return l.getHash().toBase64().compareTo(r.getHash().toBase64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ObjectCounter<String>> loadInBackground() {
|
||||||
|
List<ObjectCounter<String>> ret = new ArrayList<ObjectCounter<String>>();
|
||||||
|
|
||||||
|
ObjectCounter<String> versions = new ObjectCounter<String>();
|
||||||
|
ObjectCounter<String> countries = new ObjectCounter<String>();
|
||||||
|
ObjectCounter<String> transports = new ObjectCounter<String>();
|
||||||
|
|
||||||
|
if (mRContext.netDb().isInitialized()) {
|
||||||
|
Hash us = mRContext.routerHash();
|
||||||
|
|
||||||
|
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
|
||||||
|
routers.addAll(mRContext.netDb().getRouters());
|
||||||
|
for (RouterInfo ri : routers) {
|
||||||
|
Hash key = ri.getHash();
|
||||||
|
if (!key.equals(us)) {
|
||||||
|
String routerVersion = ri.getOption("router.version");
|
||||||
|
if (routerVersion != null)
|
||||||
|
versions.increment(routerVersion);
|
||||||
|
// XXX Disabled, no GeoIP file
|
||||||
|
String country = null;//mRContext.commSystem().getCountry(key);
|
||||||
|
if(country != null)
|
||||||
|
countries.increment(country);
|
||||||
|
transports.increment(classifyTransports(ri));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.add(versions);
|
||||||
|
ret.add(countries);
|
||||||
|
ret.add(transports);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int SSU = 1;
|
||||||
|
private static final int SSUI = 2;
|
||||||
|
private static final int NTCP = 4;
|
||||||
|
private static final int IPV6 = 8;
|
||||||
|
private static final String[] TNAMES = { "Hidden or starting up", "SSU", "SSU with introducers", "",
|
||||||
|
"NTCP", "NTCP and SSU", "NTCP and SSU with introducers", "",
|
||||||
|
"", "IPv6 SSU", "IPv6 Only SSU, introducers", "IPv6 SSU, introducers",
|
||||||
|
"IPv6 NTCP", "IPv6 NTCP, SSU", "IPv6 Only NTCP, SSU, introducers", "IPv6 NTCP, SSU, introducers" };
|
||||||
|
/**
|
||||||
|
* what transport types
|
||||||
|
*/
|
||||||
|
private static String classifyTransports(RouterInfo info) {
|
||||||
|
int rv = 0;
|
||||||
|
for (RouterAddress addr : info.getAddresses()) {
|
||||||
|
String style = addr.getTransportStyle();
|
||||||
|
if (style.equals("NTCP")) {
|
||||||
|
rv |= NTCP;
|
||||||
|
} else if (style.equals("SSU")) {
|
||||||
|
if (addr.getOption("iport0") != null)
|
||||||
|
rv |= SSUI;
|
||||||
|
else
|
||||||
|
rv |= SSU;
|
||||||
|
}
|
||||||
|
String host = addr.getHost();
|
||||||
|
if (host != null && host.contains(":"))
|
||||||
|
rv |= IPV6;
|
||||||
|
|
||||||
|
}
|
||||||
|
return TNAMES[rv];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(List<ObjectCounter<String>> data) {
|
||||||
|
if (isReset()) {
|
||||||
|
// The Loader has been reset; ignore the result and invalidate the data.
|
||||||
|
if (data != null) {
|
||||||
|
releaseResources(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||||
|
// We must protect it until the new data has been delivered.
|
||||||
|
List<ObjectCounter<String>> oldData = mData;
|
||||||
|
mData = data;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
// If the Loader is in a started state, have the superclass deliver the
|
||||||
|
// results to the client.
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the old data as we don't need it any more.
|
||||||
|
if (oldData != null && oldData != data) {
|
||||||
|
releaseResources(oldData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mData != null) {
|
||||||
|
// Deliver any previously loaded data immediately.
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeContentChanged() || mData == null) {
|
||||||
|
// When the observer detects a change, it should call onContentChanged()
|
||||||
|
// on the Loader, which will cause the next call to takeContentChanged()
|
||||||
|
// to return true. If this is ever the case (or if the current data is
|
||||||
|
// null), we force a new load.
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||||
|
// current load (if there is one).
|
||||||
|
cancelLoad();
|
||||||
|
|
||||||
|
// Note that we leave the observer as is. Loaders in a stopped state
|
||||||
|
// should still monitor the data source for changes so that the Loader
|
||||||
|
// will know to force a new load if it is ever started again.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
// Ensure the loader has been stopped.
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
// At this point we can release the resources associated with 'mData'.
|
||||||
|
if (mData != null) {
|
||||||
|
releaseResources(mData);
|
||||||
|
mData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCanceled(List<ObjectCounter<String>> data) {
|
||||||
|
// Attempt to cancel the current asynchronous load.
|
||||||
|
super.onCanceled(data);
|
||||||
|
|
||||||
|
// The load has been canceled, so we should release the resources
|
||||||
|
// associated with 'data'.
|
||||||
|
releaseResources(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseResources(List<ObjectCounter<String>> data) {
|
||||||
|
// For a simple List, there is nothing to do. For something like a Cursor, we
|
||||||
|
// would close it in this method. All resources associated with the Loader
|
||||||
|
// should be released here.
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.util.ObjectCounter;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
|
||||||
|
LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>> {
|
||||||
|
private NetDbPagerAdapter mNetDbPagerAdapter;
|
||||||
|
ViewPager mViewPager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.parentfragment_viewpager, container, false);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
// Set up NetDbPagerAdapter containing the categories
|
||||||
|
mNetDbPagerAdapter = new NetDbPagerAdapter(getChildFragmentManager());
|
||||||
|
|
||||||
|
// Set up ViewPager for swiping between categories
|
||||||
|
mViewPager = (ViewPager) getActivity().findViewById(R.id.pager);
|
||||||
|
mViewPager.setAdapter(mNetDbPagerAdapter);
|
||||||
|
mViewPager.setOnPageChangeListener(
|
||||||
|
new ViewPager.SimpleOnPageChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
mViewPager.setCurrentItem(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRouterConnectionReady() {
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRouterConnectionNotReady() {
|
||||||
|
Util.d("Router not running or not bound to NetDbSummaryPagerFragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_netdb_list_actions, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle presses on the action bar items
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_refresh:
|
||||||
|
if (getRouterContext() != null) {
|
||||||
|
Util.d("Refresh called, restarting Loader");
|
||||||
|
mNetDbPagerAdapter.setData(null);
|
||||||
|
mViewPager.invalidate();
|
||||||
|
getLoaderManager().restartLoader(0, null, this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NetDbPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
private List<ObjectCounter<String>> mData;
|
||||||
|
|
||||||
|
public NetDbPagerAdapter(FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<ObjectCounter<String>> data) {
|
||||||
|
mData = data;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int i) {
|
||||||
|
if (mData == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return NetDbSummaryTableFragment.newInstance(i, mData.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
if (mData == null)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int i) {
|
||||||
|
switch (i) {
|
||||||
|
case 1:
|
||||||
|
return "Countries";
|
||||||
|
case 2:
|
||||||
|
return "Transports";
|
||||||
|
default:
|
||||||
|
return "Versions";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>>
|
||||||
|
|
||||||
|
public Loader<List<ObjectCounter<String>>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new NetDbStatsLoader(getActivity(), getRouterContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoadFinished(Loader<List<ObjectCounter<String>>> loader,
|
||||||
|
List<ObjectCounter<String>> data) {
|
||||||
|
mNetDbPagerAdapter.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoaderReset(Loader<List<ObjectCounter<String>>> loader) {
|
||||||
|
mNetDbPagerAdapter.setData(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package net.i2p.android.router.netdb;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.util.ObjectCounter;
|
||||||
|
import net.i2p.util.VersionComparator;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TableLayout;
|
||||||
|
import android.widget.TableRow;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class NetDbSummaryTableFragment extends Fragment {
|
||||||
|
private static final String CATEGORY = "category";
|
||||||
|
private static final String COUNTS = "counts";
|
||||||
|
|
||||||
|
private int mCategory;
|
||||||
|
private ObjectCounter<String> mCounts;
|
||||||
|
private TableLayout mTable;
|
||||||
|
|
||||||
|
public static NetDbSummaryTableFragment newInstance(int category,
|
||||||
|
ObjectCounter<String> counts) {
|
||||||
|
NetDbSummaryTableFragment f = new NetDbSummaryTableFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(CATEGORY, category);
|
||||||
|
args.putSerializable(COUNTS, counts);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_table, container, false);
|
||||||
|
|
||||||
|
mCategory = getArguments().getInt(CATEGORY);
|
||||||
|
mCounts = (ObjectCounter<String>) getArguments().getSerializable(COUNTS);
|
||||||
|
|
||||||
|
mTable = (TableLayout) v.findViewById(R.id.table);
|
||||||
|
|
||||||
|
List<String> objects = new ArrayList<String>(mCounts.objects());
|
||||||
|
if (!objects.isEmpty()) {
|
||||||
|
createTableTitle();
|
||||||
|
|
||||||
|
switch (mCategory) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
Collections.sort(objects);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Collections.sort(objects,
|
||||||
|
Collections.reverseOrder(new VersionComparator()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String object : objects) {
|
||||||
|
int num = mCounts.count(object);
|
||||||
|
addTableRow(object, ""+num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTableTitle() {
|
||||||
|
TableRow titleRow;
|
||||||
|
TextView tl1, tl2;
|
||||||
|
|
||||||
|
titleRow = new TableRow(getActivity());
|
||||||
|
titleRow.setPadding(10, 0, 0, 0);
|
||||||
|
|
||||||
|
tl1 = new TextView(getActivity());
|
||||||
|
tl1.setTextSize(20);
|
||||||
|
tl2 = new TextView(getActivity());
|
||||||
|
tl2.setTextSize(20);
|
||||||
|
|
||||||
|
switch (mCategory) {
|
||||||
|
case 1:
|
||||||
|
tl1.setText("Transports");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
tl1.setText("Country");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tl1.setText("Version");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tl2.setText("Count");
|
||||||
|
|
||||||
|
titleRow.addView(tl1);
|
||||||
|
titleRow.addView(tl2);
|
||||||
|
|
||||||
|
mTable.addView(titleRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTableRow(String name, String count) {
|
||||||
|
TableRow row;
|
||||||
|
TextView tl1, tl2;
|
||||||
|
|
||||||
|
row = new TableRow(getActivity());
|
||||||
|
row.setPadding(10, 0, 0, 0);
|
||||||
|
|
||||||
|
tl1 = new TextView(getActivity());
|
||||||
|
tl2 = new TextView(getActivity());
|
||||||
|
|
||||||
|
tl1.setText(name);
|
||||||
|
tl2.setText(count);
|
||||||
|
|
||||||
|
row.addView(tl1);
|
||||||
|
row.addView(tl2);
|
||||||
|
|
||||||
|
mTable.addView(row);
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ import android.content.SharedPreferences;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -14,7 +13,6 @@ import java.io.OutputStream;
|
|||||||
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 net.i2p.android.apps.EepGetFetcher;
|
import net.i2p.android.apps.EepGetFetcher;
|
||||||
import net.i2p.android.router.util.AppCache;
|
import net.i2p.android.router.util.AppCache;
|
||||||
import net.i2p.android.router.util.Util;
|
import net.i2p.android.router.util.Util;
|
||||||
@ -47,7 +45,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
//private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt()));
|
//private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt()));
|
||||||
private static final String NONCE = "0";
|
private static final String NONCE = "0";
|
||||||
private static final String SCHEME = "content";
|
private static final String SCHEME = "content";
|
||||||
public static final String AUTHORITY = "net.i2p.android.router";
|
public static final String AUTHORITY = "net.i2p.android";
|
||||||
/** includes the nonce */
|
/** includes the nonce */
|
||||||
public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE);
|
public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE);
|
||||||
|
|
||||||
@ -83,7 +81,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
if (q == null || !a.equals(AUTHORITY))
|
if (q == null || !a.equals(AUTHORITY))
|
||||||
return key;
|
return key;
|
||||||
if (p.contains(QUERY_MARKER)) {
|
if (p.contains(QUERY_MARKER)) {
|
||||||
Util.e("Key contains both queries ?!? " + key);
|
Util.d("Key contains both queries ?!? " + key);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// twizzle query
|
// twizzle query
|
||||||
@ -112,7 +110,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
Util.e("CacheProvider open " + uri);
|
Util.d("CacheProvider open " + uri);
|
||||||
|
|
||||||
// if uri is malformed and we have a current base, rectify it
|
// if uri is malformed and we have a current base, rectify it
|
||||||
uri = rectifyContentUri(getCurrentBase(), uri);
|
uri = rectifyContentUri(getCurrentBase(), uri);
|
||||||
@ -123,17 +121,17 @@ public class CacheProvider extends ContentProvider {
|
|||||||
try {
|
try {
|
||||||
File file = new File(filePath);
|
File file = new File(filePath);
|
||||||
if (file.exists())
|
if (file.exists())
|
||||||
Util.e("CacheProvider returning " + file);
|
Util.d("CacheProvider returning " + file);
|
||||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
} catch (FileNotFoundException fnfe) {
|
} catch (FileNotFoundException fnfe) {
|
||||||
Util.e("CacheProvider not found", fnfe);
|
Util.d("CacheProvider not found", fnfe);
|
||||||
remove(uri);
|
remove(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Util.e("CacheProvider not in cache " + uri);
|
Util.d("CacheProvider not in cache " + uri);
|
||||||
|
|
||||||
Uri newUri = getI2PUri(uri);
|
Uri newUri = getI2PUri(uri);
|
||||||
Util.e("CacheProvider fetching: " + newUri);
|
Util.d("CacheProvider fetching: " + newUri);
|
||||||
return eepFetch(newUri);
|
return eepFetch(newUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +161,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String debug = "CacheProvider nonce: " + nonce + " scheme: " + scheme + " host: " + host + " realPath: " + realPath + " query: " + query;
|
String debug = "CacheProvider nonce: " + nonce + " scheme: " + scheme + " host: " + host + " realPath: " + realPath + " query: " + query;
|
||||||
Util.e(debug);
|
Util.d(debug);
|
||||||
if ((!NONCE.equals(nonce)) ||
|
if ((!NONCE.equals(nonce)) ||
|
||||||
(!"http".equals(scheme)) ||
|
(!"http".equals(scheme)) ||
|
||||||
(host == null) ||
|
(host == null) ||
|
||||||
@ -186,7 +184,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
* or the original uri on error, or if no rectification needed
|
* or the original uri on error, or if no rectification needed
|
||||||
*/
|
*/
|
||||||
public static Uri rectifyContentUri(Uri base, Uri uri) {
|
public static Uri rectifyContentUri(Uri base, Uri uri) {
|
||||||
Util.e("rectifyContentUri base: " + base + " and uri: " + uri);
|
Util.d("rectifyContentUri base: " + base + " and uri: " + uri);
|
||||||
if (base == null)
|
if (base == null)
|
||||||
return uri;
|
return uri;
|
||||||
if (!SCHEME.equals(base.getScheme()))
|
if (!SCHEME.equals(base.getScheme()))
|
||||||
@ -224,7 +222,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
buf.append(path);
|
buf.append(path);
|
||||||
if (query != null)
|
if (query != null)
|
||||||
buf.append(QUERY_MARKER).append(query);
|
buf.append(QUERY_MARKER).append(query);
|
||||||
Util.e("rectified from base: " + base + " and uri: " + uri + " to: " + buf);
|
Util.d("rectified from base: " + base + " and uri: " + uri + " to: " + buf);
|
||||||
return Uri.parse(buf.toString());
|
return Uri.parse(buf.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,23 +245,23 @@ public class CacheProvider extends ContentProvider {
|
|||||||
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
return parcel;
|
return parcel;
|
||||||
} else {
|
} else {
|
||||||
Util.e("CacheProvider Sucess but no data " + uri);
|
Util.d("CacheProvider Sucess but no data " + uri);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Util.e("CacheProvider Eepget fail " + uri);
|
Util.d("CacheProvider Eepget fail " + uri);
|
||||||
}
|
}
|
||||||
AppCache.getInstance().removeCacheFile(uri);
|
AppCache.getInstance().removeCacheFile(uri);
|
||||||
throw new FileNotFoundException("eepget fail");
|
throw new FileNotFoundException("eepget fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||||
Util.e("CacheProvider delete " + uri);
|
Util.d("CacheProvider delete " + uri);
|
||||||
boolean deleted = remove(uri);
|
boolean deleted = remove(uri);
|
||||||
return deleted ? 1 : 0;
|
return deleted ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getType(Uri uri) {
|
public String getType(Uri uri) {
|
||||||
Util.e("CacheProvider getType " + uri);
|
Util.d("CacheProvider getType " + uri);
|
||||||
return "text/html";
|
return "text/html";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,12 +271,12 @@ public class CacheProvider extends ContentProvider {
|
|||||||
public Uri insert(Uri uri, ContentValues values) {
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
String fileURI = values.getAsString(DATA);
|
String fileURI = values.getAsString(DATA);
|
||||||
if (fileURI != null) {
|
if (fileURI != null) {
|
||||||
Util.e("CacheProvider insert " + uri);
|
Util.d("CacheProvider insert " + uri);
|
||||||
put(uri, fileURI);
|
put(uri, fileURI);
|
||||||
}
|
}
|
||||||
Boolean setAsCurrentBase = values.getAsBoolean(CURRENT_BASE);
|
Boolean setAsCurrentBase = values.getAsBoolean(CURRENT_BASE);
|
||||||
if (setAsCurrentBase != null && setAsCurrentBase.booleanValue()) {
|
if (setAsCurrentBase != null && setAsCurrentBase.booleanValue()) {
|
||||||
Util.e("CacheProvider set current base " + uri);
|
Util.d("CacheProvider set current base " + uri);
|
||||||
setCurrentBase(uri);
|
setCurrentBase(uri);
|
||||||
}
|
}
|
||||||
return uri;
|
return uri;
|
||||||
@ -291,7 +289,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selecctionArgs, String sortOrder) {
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selecctionArgs, String sortOrder) {
|
||||||
Util.e("CacheProvider query " + uri);
|
Util.d("CacheProvider query " + uri);
|
||||||
// TODO return a MatrixCursor with a _data entry
|
// TODO return a MatrixCursor with a _data entry
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -304,7 +302,7 @@ public class CacheProvider extends ContentProvider {
|
|||||||
|
|
||||||
private void cleanup() {
|
private void cleanup() {
|
||||||
String pfx = CONTENT_URI.toString();
|
String pfx = CONTENT_URI.toString();
|
||||||
List<String> toDelete = new ArrayList();
|
List<String> toDelete = new ArrayList<String>();
|
||||||
Map<String, ?> map = _sharedPrefs.getAll();
|
Map<String, ?> map = _sharedPrefs.getAll();
|
||||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||||
String path = (String) e.getValue();
|
String path = (String) e.getValue();
|
@ -9,8 +9,7 @@ import android.content.ServiceConnection;
|
|||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import net.i2p.android.router.service.RouterBinder;
|
||||||
import net.i2p.android.router.binder.RouterBinder;
|
|
||||||
import net.i2p.android.router.service.RouterService;
|
import net.i2p.android.router.service.RouterService;
|
||||||
import net.i2p.android.router.util.Util;
|
import net.i2p.android.router.util.Util;
|
||||||
|
|
||||||
@ -32,13 +31,14 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
intents.addAction(Intent.ACTION_TIME_CHANGED);
|
intents.addAction(Intent.ACTION_TIME_CHANGED);
|
||||||
intents.addAction(Intent.ACTION_TIME_TICK); // once per minute, for testing
|
intents.addAction(Intent.ACTION_TIME_TICK); // once per minute, for testing
|
||||||
intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||||
context.registerReceiver(this, intents);
|
@SuppressWarnings("LeakingThisInConstructor")
|
||||||
|
Intent registerReceiver = context.registerReceiver(this, intents);
|
||||||
_wasConnected = Util.isConnected(context);
|
_wasConnected = Util.isConnected(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
System.err.println("Got broadcast: " + action);
|
//Util.w("Got broadcast: " + action);
|
||||||
|
|
||||||
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
|
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
|
||||||
boolean failover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
|
boolean failover = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
|
||||||
@ -47,7 +47,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
NetworkInfo other = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
|
NetworkInfo other = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
|
||||||
|
|
||||||
/*****
|
/*****
|
||||||
System.err.println("No conn? " + noConn + " failover? " + failover +
|
Util.w("No conn? " + noConn + " failover? " + failover +
|
||||||
" info: " + info + " other: " + other);
|
" info: " + info + " other: " + other);
|
||||||
printInfo(info);
|
printInfo(info);
|
||||||
printInfo(other);
|
printInfo(other);
|
||||||
@ -63,10 +63,10 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
if (++_unconnectedCount >= 3) {
|
if (++_unconnectedCount >= 3) {
|
||||||
RouterService svc = _routerService;
|
RouterService svc = _routerService;
|
||||||
if (_isBound && svc != null) {
|
if (_isBound && svc != null) {
|
||||||
System.err.println("********* Network down, already bound");
|
Util.w("********* Network down, already bound");
|
||||||
svc.networkStop();
|
svc.networkStop();
|
||||||
} else {
|
} else {
|
||||||
System.err.println("********* Network down, binding to router");
|
Util.w("********* Network down, binding to router");
|
||||||
// connection will call networkStop()
|
// connection will call networkStop()
|
||||||
bindRouter();
|
bindRouter();
|
||||||
}
|
}
|
||||||
@ -81,10 +81,10 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
/****
|
/****
|
||||||
private static void printInfo(NetworkInfo ni) {
|
private static void printInfo(NetworkInfo ni) {
|
||||||
if (ni == null) {
|
if (ni == null) {
|
||||||
System.err.println("Network info is null");
|
Util.w("Network info is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.err.println(
|
Util.w(
|
||||||
"state: " + ni.getState() +
|
"state: " + ni.getState() +
|
||||||
" detail: " + ni.getDetailedState() +
|
" detail: " + ni.getDetailedState() +
|
||||||
" extrainfo: " + ni.getExtraInfo() +
|
" extrainfo: " + ni.getExtraInfo() +
|
||||||
@ -101,10 +101,10 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
private boolean bindRouter() {
|
private boolean bindRouter() {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.setClassName(_context, "net.i2p.android.router.service.RouterService");
|
intent.setClassName(_context, "net.i2p.android.router.service.RouterService");
|
||||||
System.err.println(this + " calling bindService");
|
Util.w(this + " calling bindService");
|
||||||
_connection = new RouterConnection();
|
_connection = new RouterConnection();
|
||||||
boolean success = _context.bindService(intent, _connection, 0);
|
boolean success = _context.bindService(intent, _connection, 0);
|
||||||
System.err.println(this + " got from bindService: " + success);
|
Util.w(this + " got from bindService: " + success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
_isBound = true;
|
_isBound = true;
|
||||||
_unconnectedCount = 0;
|
_unconnectedCount = 0;
|
||||||
_wasConnected = false;
|
_wasConnected = false;
|
||||||
System.err.println("********* Network down, stopping router");
|
Util.w("********* Network down, stopping router");
|
||||||
_routerService.networkStop();
|
_routerService.networkStop();
|
||||||
// this doesn't work here... TODO where to unbind
|
// this doesn't work here... TODO where to unbind
|
||||||
//_context.unbindService(this);
|
//_context.unbindService(this);
|
||||||
@ -132,7 +132,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
|||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
_isBound = false;
|
_isBound = false;
|
||||||
_routerService = null;
|
_routerService = null;
|
||||||
System.err.println("********* Receiver unbinding from router");
|
Util.w("********* Receiver unbinding from router");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
42
app/src/main/java/net/i2p/android/router/service/Init.java
Normal file
42
app/src/main/java/net/i2p/android/router/service/Init.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import java.io.File;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.util.FileUtil;
|
||||||
|
|
||||||
|
class Init {
|
||||||
|
|
||||||
|
private final Context ctx;
|
||||||
|
private final String myDir;
|
||||||
|
private final String _ourVersion;
|
||||||
|
|
||||||
|
public Init(Context c) {
|
||||||
|
ctx = c;
|
||||||
|
myDir = c.getFilesDir().getAbsolutePath();
|
||||||
|
_ourVersion = Util.getOurVersion(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
|
||||||
|
deleteOldFiles();
|
||||||
|
|
||||||
|
// Set up the locations so Router and WorkingDir can find them
|
||||||
|
// We do this again here, in the event settings were changed.
|
||||||
|
System.setProperty("i2p.dir.base", myDir);
|
||||||
|
System.setProperty("i2p.dir.config", myDir);
|
||||||
|
System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteOldFiles() {
|
||||||
|
File tmp = new File(myDir, "tmp");
|
||||||
|
File[] files = tmp.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
File f = files[i];
|
||||||
|
Util.d("Deleting old file/dir " + f);
|
||||||
|
FileUtil.rmdir(f, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,13 @@
|
|||||||
package net.i2p.android.router.service;
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.BOB.BOB;
|
||||||
import net.i2p.addressbook.DaemonThread;
|
import net.i2p.addressbook.DaemonThread;
|
||||||
import net.i2p.android.apps.NewsFetcher;
|
import net.i2p.android.apps.NewsFetcher;
|
||||||
|
import net.i2p.android.router.util.Notifications;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||||
import net.i2p.router.Job;
|
import net.i2p.router.Job;
|
||||||
import net.i2p.router.JobImpl;
|
import net.i2p.router.JobImpl;
|
||||||
@ -28,28 +34,34 @@ import net.i2p.util.I2PAppThread;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class LoadClientsJob extends JobImpl {
|
class LoadClientsJob extends JobImpl {
|
||||||
|
|
||||||
private Thread _fetcherThread;
|
private Notifications _notif;
|
||||||
private DaemonThread _addressbook;
|
private DaemonThread _addressbook;
|
||||||
|
private BOB _bob;
|
||||||
|
|
||||||
/** this is the delay to load (and start) the clients. */
|
/** this is the delay to load (and start) the clients. */
|
||||||
private static final long LOAD_DELAY = 90*1000;
|
private static final long LOAD_DELAY = 90*1000;
|
||||||
|
|
||||||
|
|
||||||
public LoadClientsJob(RouterContext ctx) {
|
public LoadClientsJob(RouterContext ctx, Notifications notif) {
|
||||||
super(ctx);
|
super(ctx);
|
||||||
|
_notif = notif;
|
||||||
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
|
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() { return "Start Clients"; };
|
public String getName() { return "Start Clients"; }
|
||||||
|
|
||||||
public void runJob() {
|
public void runJob() {
|
||||||
Job j = new RunI2PTunnel(getContext());
|
Job j = new RunI2PTunnel(getContext());
|
||||||
getContext().jobQueue().addJob(j);
|
getContext().jobQueue().addJob(j);
|
||||||
|
|
||||||
NewsFetcher fetcher = NewsFetcher.getInstance(getContext());
|
Thread t = new I2PAppThread(new StatSummarizer(), "StatSummarizer", true);
|
||||||
_fetcherThread = new I2PAppThread(fetcher, "NewsFetcher", true);
|
t.setPriority(Thread.NORM_PRIORITY - 1);
|
||||||
_fetcherThread.start();
|
t.start();
|
||||||
|
|
||||||
|
NewsFetcher fetcher = NewsFetcher.getInstance(getContext(), _notif);
|
||||||
|
t = new I2PAppThread(fetcher, "NewsFetcher", true);
|
||||||
|
t.start();
|
||||||
|
|
||||||
_addressbook = new DaemonThread(new String[] {"addressbook"});
|
_addressbook = new DaemonThread(new String[] {"addressbook"});
|
||||||
_addressbook.setName("Addressbook");
|
_addressbook.setName("Addressbook");
|
||||||
@ -57,7 +69,10 @@ class LoadClientsJob extends JobImpl {
|
|||||||
_addressbook.start();
|
_addressbook.start();
|
||||||
|
|
||||||
// add other clients here
|
// add other clients here
|
||||||
|
_bob = new BOB(I2PAppContext.getGlobalContext(), null, new String[0]);
|
||||||
|
try {
|
||||||
|
_bob.startup();
|
||||||
|
} catch (IOException ioe) {}
|
||||||
|
|
||||||
getContext().addShutdownTask(new ClientShutdownHook());
|
getContext().addShutdownTask(new ClientShutdownHook());
|
||||||
}
|
}
|
||||||
@ -68,23 +83,25 @@ class LoadClientsJob extends JobImpl {
|
|||||||
super(ctx);
|
super(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() { return "Start I2P Tunnel"; };
|
public String getName() { return "Start I2P Tunnel"; }
|
||||||
|
|
||||||
public void runJob() {
|
public void runJob() {
|
||||||
System.err.println("Starting i2ptunnel");
|
Util.d("Starting i2ptunnel");
|
||||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||||
int sz = tcg.getControllers().size();
|
int sz = tcg.getControllers().size();
|
||||||
System.err.println("i2ptunnel started " + sz + " clients");
|
Util.d("i2ptunnel started " + sz + " clients");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClientShutdownHook implements Runnable {
|
private class ClientShutdownHook implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
System.err.println("client shutdown hook");
|
Util.d("client shutdown hook");
|
||||||
// i2ptunnel registers its own hook
|
// i2ptunnel registers its own hook
|
||||||
if (_fetcherThread != null)
|
// StatSummarizer registers its own hook
|
||||||
_fetcherThread.interrupt();
|
// NewsFetcher registers its own hook
|
||||||
|
if (_bob != null)
|
||||||
|
_bob.shutdown(null);
|
||||||
if (_addressbook != null)
|
if (_addressbook != null)
|
||||||
_addressbook.halt();
|
_addressbook.halt();
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package net.i2p.android.router.binder;
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
|
||||||
import net.i2p.android.router.service.RouterService;
|
|
||||||
|
|
||||||
public class RouterBinder extends Binder {
|
public class RouterBinder extends Binder {
|
||||||
|
|
||||||
private final RouterService _routerService;
|
private final RouterService _routerService;
|
@ -0,0 +1,792 @@
|
|||||||
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.RemoteCallbackList;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.receiver.I2PReceiver;
|
||||||
|
import net.i2p.android.router.util.Notifications;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.router.Job;
|
||||||
|
import net.i2p.router.Router;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.router.RouterLaunch;
|
||||||
|
import net.i2p.util.OrderedProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the router
|
||||||
|
*/
|
||||||
|
public class RouterService extends Service {
|
||||||
|
|
||||||
|
private RouterContext _context;
|
||||||
|
private String _myDir;
|
||||||
|
//private String _apkPath;
|
||||||
|
private State _state = State.INIT;
|
||||||
|
private Thread _starterThread;
|
||||||
|
private StatusBar _statusBar;
|
||||||
|
private Notifications _notif;
|
||||||
|
private I2PReceiver _receiver;
|
||||||
|
private IBinder _binder;
|
||||||
|
private final Object _stateLock = new Object();
|
||||||
|
private Handler _handler;
|
||||||
|
private Runnable _updater;
|
||||||
|
private boolean mStartCalled;
|
||||||
|
private static final String SHARED_PREFS = "net.i2p.android.router";
|
||||||
|
private static final String LAST_STATE = "service.lastState";
|
||||||
|
private static final String EXTRA_RESTART = "restart";
|
||||||
|
private static final String MARKER = "************************************** ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a list of callbacks that have been registered with the
|
||||||
|
* service. Note that this is package scoped (instead of private) so
|
||||||
|
* that it can be accessed more efficiently from inner classes.
|
||||||
|
*/
|
||||||
|
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
|
||||||
|
= new RemoteCallbackList<IRouterStateCallback>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
mStartCalled = false;
|
||||||
|
State lastState = getSavedState();
|
||||||
|
setState(State.INIT);
|
||||||
|
Util.d(this + " onCreate called"
|
||||||
|
+ " Saved state is: " + lastState
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
|
||||||
|
//(new File(getFilesDir(), "wrapper.log")).delete();
|
||||||
|
_myDir = getFilesDir().getAbsolutePath();
|
||||||
|
// init other stuff here, delete log, etc.
|
||||||
|
Init init = new Init(this);
|
||||||
|
init.initialize();
|
||||||
|
//_apkPath = init.getAPKPath();
|
||||||
|
_statusBar = new StatusBar(this);
|
||||||
|
// Remove stale notification icon.
|
||||||
|
_statusBar.remove();
|
||||||
|
_notif = new Notifications(this);
|
||||||
|
_binder = new RouterBinder(this);
|
||||||
|
_handler = new Handler();
|
||||||
|
_updater = new Updater();
|
||||||
|
if(lastState == State.RUNNING || lastState == State.ACTIVE) {
|
||||||
|
Intent intent = new Intent(this, RouterService.class);
|
||||||
|
intent.putExtra(EXTRA_RESTART, true);
|
||||||
|
onStartCommand(intent, 12345, 67890);
|
||||||
|
} else if(lastState == State.MANUAL_QUITTING) {
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
setState(State.MANUAL_QUITTED);
|
||||||
|
stopSelf(); // Die.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT called by system if it restarts us after a crash
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
Util.d(this + " onStart called"
|
||||||
|
+ " Intent is: " + intent
|
||||||
|
+ " Flags is: " + flags
|
||||||
|
+ " ID is: " + startId
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
mStartCalled = true;
|
||||||
|
boolean restart = intent != null && intent.getBooleanExtra(EXTRA_RESTART, false);
|
||||||
|
if(restart) {
|
||||||
|
Util.d(this + " RESTARTING");
|
||||||
|
}
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(_state != State.INIT) //return START_STICKY;
|
||||||
|
{
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
_receiver = new I2PReceiver(this);
|
||||||
|
if(Util.isConnected(this)) {
|
||||||
|
if(restart) {
|
||||||
|
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting");
|
||||||
|
} else {
|
||||||
|
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
|
||||||
|
}
|
||||||
|
setState(State.STARTING);
|
||||||
|
_starterThread = new Thread(new Starter());
|
||||||
|
_starterThread.start();
|
||||||
|
} else {
|
||||||
|
_statusBar.replace(StatusBar.ICON_WAITING_NETWORK, "I2P is waiting for a network connection");
|
||||||
|
setState(State.WAITING);
|
||||||
|
_handler.postDelayed(new Waiter(), 10 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_handler.removeCallbacks(_updater);
|
||||||
|
_handler.postDelayed(_updater, 50);
|
||||||
|
if(!restart) {
|
||||||
|
startForeground(1337, _statusBar.getNote());
|
||||||
|
}
|
||||||
|
|
||||||
|
//return START_STICKY;
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maybe this goes away when the receiver can bind to us
|
||||||
|
*/
|
||||||
|
private class Waiter implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Util.d(MARKER + this + " waiter handler"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
if(_state == State.WAITING) {
|
||||||
|
if(Util.isConnected(RouterService.this)) {
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(_state != State.WAITING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_statusBar.replace(StatusBar.ICON_STARTING, "Network connected, I2P is starting up");
|
||||||
|
setState(State.STARTING);
|
||||||
|
_starterThread = new Thread(new Starter());
|
||||||
|
_starterThread.start();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_handler.postDelayed(this, 15 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Starter implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Util.d(MARKER + this + " starter thread"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
//Util.d(MARKER + this + " JBigI speed test started");
|
||||||
|
//NativeBigInteger.main(null);
|
||||||
|
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
|
||||||
|
|
||||||
|
|
||||||
|
// Before we launch, fix up any settings that need to be fixed here.
|
||||||
|
// This should be done in the core, but as of this writing it isn't!
|
||||||
|
|
||||||
|
// Step one. Load the propertites.
|
||||||
|
Properties props = new OrderedProperties();
|
||||||
|
Properties oldprops = new OrderedProperties();
|
||||||
|
String wrapName = _myDir + "/router.config";
|
||||||
|
try {
|
||||||
|
InputStream fin = new FileInputStream(new File(wrapName));
|
||||||
|
DataHelper.loadProps(props, fin);
|
||||||
|
} catch(IOException ioe) {
|
||||||
|
// shouldn't happen...
|
||||||
|
}
|
||||||
|
oldprops.putAll(props);
|
||||||
|
// Step two, check for any port settings, and copy for those that are missing.
|
||||||
|
int UDPinbound;
|
||||||
|
int UDPinlocal;
|
||||||
|
int TCPinbound;
|
||||||
|
int TCPinlocal;
|
||||||
|
UDPinbound = Integer.parseInt(props.getProperty("i2np.udp.port", "-1"));
|
||||||
|
UDPinlocal = Integer.parseInt(props.getProperty("i2np.udp.internalPort", "-1"));
|
||||||
|
TCPinbound = Integer.parseInt(props.getProperty("i2np.ntcp.port", "-1"));
|
||||||
|
TCPinlocal = Integer.parseInt(props.getProperty("i2np.ntcp.internalPort", "-1"));
|
||||||
|
boolean hasUDPinbound = UDPinbound != -1;
|
||||||
|
boolean hasUDPinlocal = UDPinlocal != -1;
|
||||||
|
boolean hasTCPinbound = TCPinbound != -1;
|
||||||
|
boolean hasTCPinlocal = TCPinlocal != -1;
|
||||||
|
|
||||||
|
// check and clear values based on these:
|
||||||
|
boolean udp = Boolean.parseBoolean(props.getProperty("i2np.udp.enable", "false"));
|
||||||
|
boolean tcp = Boolean.parseBoolean(props.getProperty("i2np.ntcp.enable", "false"));
|
||||||
|
|
||||||
|
// Fix if both are false.
|
||||||
|
if(!(udp || tcp)) {
|
||||||
|
// If both are not on, turn them both on.
|
||||||
|
props.setProperty("i2np.udp.enable", "true");
|
||||||
|
props.setProperty("i2np.ntcp.enable", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix if we have local but no inbound
|
||||||
|
if(!hasUDPinbound && hasUDPinlocal) {
|
||||||
|
// if we got a local port and no external port, set it
|
||||||
|
hasUDPinbound = true;
|
||||||
|
UDPinbound = UDPinlocal;
|
||||||
|
}
|
||||||
|
if(!hasTCPinbound && hasTCPinlocal) {
|
||||||
|
// if we got a local port and no external port, set it
|
||||||
|
hasTCPinbound = true;
|
||||||
|
TCPinbound = TCPinlocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean anyUDP = hasUDPinbound || hasUDPinlocal;
|
||||||
|
boolean anyTCP = hasTCPinbound || hasTCPinlocal;
|
||||||
|
boolean anyport = anyUDP || anyTCP;
|
||||||
|
|
||||||
|
if(!anyport) {
|
||||||
|
// generate one for UDPinbound, and fall thru.
|
||||||
|
// FIX ME: Possibly not the best but should be OK.
|
||||||
|
Random generator = new Random(System.currentTimeMillis());
|
||||||
|
UDPinbound = generator.nextInt(55500) + 10000;
|
||||||
|
anyUDP = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy missing port numbers
|
||||||
|
if(anyUDP && !anyTCP) {
|
||||||
|
TCPinbound = UDPinbound;
|
||||||
|
TCPinlocal = UDPinlocal;
|
||||||
|
}
|
||||||
|
if(anyTCP && !anyUDP) {
|
||||||
|
UDPinbound = TCPinbound;
|
||||||
|
UDPinlocal = TCPinlocal;
|
||||||
|
}
|
||||||
|
// reset for a retest.
|
||||||
|
hasUDPinbound = UDPinbound != -1;
|
||||||
|
hasUDPinlocal = UDPinlocal != -1;
|
||||||
|
hasTCPinbound = TCPinbound != -1;
|
||||||
|
hasTCPinlocal = TCPinlocal != -1;
|
||||||
|
anyUDP = hasUDPinbound || hasUDPinlocal;
|
||||||
|
anyTCP = hasTCPinbound || hasTCPinlocal;
|
||||||
|
boolean checkAnyUDP = anyUDP && udp;
|
||||||
|
boolean checkAnyTCP = anyTCP && tcp;
|
||||||
|
|
||||||
|
// Enable things that need to be enabled.
|
||||||
|
// Disable anything that needs to be disabled.
|
||||||
|
if(!checkAnyUDP && !checkAnyTCP) {
|
||||||
|
// enable the one(s) with values.
|
||||||
|
if(anyUDP) {
|
||||||
|
udp = true;
|
||||||
|
}
|
||||||
|
if(anyTCP) {
|
||||||
|
tcp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!udp) {
|
||||||
|
props.setProperty("i2np.udp.enable", "false");
|
||||||
|
props.remove("i2np.udp.port");
|
||||||
|
props.remove("i2np.udp.internalPort");
|
||||||
|
} else {
|
||||||
|
props.setProperty("i2np.udp.enable", "true");
|
||||||
|
if(hasUDPinbound) {
|
||||||
|
props.setProperty("i2np.udp.port", Integer.toString(UDPinbound));
|
||||||
|
} else {
|
||||||
|
props.remove("i2np.udp.port");
|
||||||
|
}
|
||||||
|
if(hasUDPinlocal) {
|
||||||
|
props.setProperty("i2np.udp.internalPort", Integer.toString(UDPinlocal));
|
||||||
|
} else {
|
||||||
|
props.remove("i2np.udp.internalPort");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!tcp) {
|
||||||
|
props.setProperty("i2np.ntcp.enable", "false");
|
||||||
|
props.remove("i2np.ntcp.port");
|
||||||
|
props.remove("i2np.ntcp.internalPort");
|
||||||
|
} else {
|
||||||
|
props.setProperty("i2np.ntcp.enable", "true");
|
||||||
|
if(hasTCPinbound) {
|
||||||
|
props.setProperty("i2np.ntcp.port", Integer.toString(TCPinbound));
|
||||||
|
} else {
|
||||||
|
props.remove("i2np.ntcp.port");
|
||||||
|
}
|
||||||
|
if(hasTCPinlocal) {
|
||||||
|
props.setProperty("i2np.ntcp.internalPort", Integer.toString(TCPinlocal));
|
||||||
|
} else {
|
||||||
|
props.remove("i2np.ntcp.internalPort");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// WHEW! Now test for any changes.
|
||||||
|
if(!props.equals(oldprops)) {
|
||||||
|
// save fixed properties.
|
||||||
|
try {
|
||||||
|
DataHelper.storeProps(props, new File(wrapName));
|
||||||
|
} catch(IOException ioe) {
|
||||||
|
// shouldn't happen...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// _NOW_ launch the router!
|
||||||
|
RouterLaunch.main(null);
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(_state != State.STARTING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(State.RUNNING);
|
||||||
|
List<?> contexts = RouterContext.listContexts();
|
||||||
|
if((contexts == null) || (contexts.isEmpty())) {
|
||||||
|
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||||
|
}
|
||||||
|
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
|
||||||
|
_context = (RouterContext) contexts.get(0);
|
||||||
|
_context.router().setKillVMOnEnd(false);
|
||||||
|
Job loadJob = new LoadClientsJob(_context, _notif);
|
||||||
|
_context.jobQueue().addJob(loadJob);
|
||||||
|
_context.addShutdownTask(new ShutdownHook());
|
||||||
|
_context.addFinalShutdownTask(new FinalShutdownHook());
|
||||||
|
_starterThread = null;
|
||||||
|
}
|
||||||
|
Util.d("Router.main finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Updater implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
RouterContext ctx = _context;
|
||||||
|
if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE)) {
|
||||||
|
Router router = ctx.router();
|
||||||
|
if(router.isAlive()) {
|
||||||
|
updateStatus(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_handler.postDelayed(this, 15 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private String _currTitle;
|
||||||
|
private boolean _hadTunnels;
|
||||||
|
|
||||||
|
private void updateStatus(RouterContext ctx) {
|
||||||
|
int active = ctx.commSystem().countActivePeers();
|
||||||
|
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||||
|
int inEx = ctx.tunnelManager().getFreeTunnelCount();
|
||||||
|
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
|
||||||
|
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
||||||
|
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
|
||||||
|
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||||
|
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
|
||||||
|
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
|
||||||
|
// control total width
|
||||||
|
DecimalFormat fmt;
|
||||||
|
if(inBW >= 1000 || outBW >= 1000) {
|
||||||
|
fmt = new DecimalFormat("#0");
|
||||||
|
} else if(inBW >= 100 || outBW >= 100) {
|
||||||
|
fmt = new DecimalFormat("#0.0");
|
||||||
|
} else {
|
||||||
|
fmt = new DecimalFormat("#0.00");
|
||||||
|
}
|
||||||
|
|
||||||
|
String text =
|
||||||
|
getResources().getString(R.string.notification_status_bw,
|
||||||
|
fmt.format(inBW), fmt.format(outBW));
|
||||||
|
|
||||||
|
String bigText =
|
||||||
|
getResources().getString(R.string.notification_status_bw,
|
||||||
|
fmt.format(inBW), fmt.format(outBW)) + '\n'
|
||||||
|
+ getResources().getString(R.string.notification_status_peers,
|
||||||
|
active, known) + '\n'
|
||||||
|
+ getResources().getString(R.string.notification_status_expl,
|
||||||
|
inEx, outEx) + '\n'
|
||||||
|
+ getResources().getString(R.string.notification_status_client,
|
||||||
|
inCl, outCl);
|
||||||
|
|
||||||
|
boolean haveTunnels = inCl > 0 && outCl > 0;
|
||||||
|
if(haveTunnels != _hadTunnels) {
|
||||||
|
if(haveTunnels) {
|
||||||
|
_currTitle = "Client tunnels are ready";
|
||||||
|
setState(State.ACTIVE);
|
||||||
|
_statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle);
|
||||||
|
} else {
|
||||||
|
_currTitle = "Client tunnels are down";
|
||||||
|
setState(State.RUNNING);
|
||||||
|
_statusBar.replace(StatusBar.ICON_RUNNING, _currTitle);
|
||||||
|
}
|
||||||
|
_hadTunnels = haveTunnels;
|
||||||
|
} else if (_currTitle == null || _currTitle.equals(""))
|
||||||
|
_currTitle = "I2P is running";
|
||||||
|
_statusBar.update(_currTitle, text, bigText);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
Util.d(this + "onBind called"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
Util.d("Intent action: " + intent.getAction());
|
||||||
|
// Select the interface to return.
|
||||||
|
if (RouterBinder.class.getName().equals(intent.getAction())) {
|
||||||
|
// Local Activity wanting access to the RouterContext
|
||||||
|
Util.d("Returning RouterContext binder");
|
||||||
|
return _binder;
|
||||||
|
}
|
||||||
|
if (IRouterState.class.getName().equals(intent.getAction())) {
|
||||||
|
// Someone wants to monitor the router state.
|
||||||
|
Util.d("Returning state binder");
|
||||||
|
return mStatusBinder;
|
||||||
|
}
|
||||||
|
Util.d("Unknown binder request, returning null");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IRouterState is defined through IDL
|
||||||
|
*/
|
||||||
|
private final IRouterState.Stub mStatusBinder = new IRouterState.Stub() {
|
||||||
|
|
||||||
|
public void registerCallback(IRouterStateCallback cb)
|
||||||
|
throws RemoteException {
|
||||||
|
if (cb != null) mStateCallbacks.register(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterCallback(IRouterStateCallback cb)
|
||||||
|
throws RemoteException {
|
||||||
|
if (cb != null) mStateCallbacks.unregister(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStarted() throws RemoteException {
|
||||||
|
return mStartCalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getState() throws RemoteException {
|
||||||
|
return _state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onUnbind(Intent intent) {
|
||||||
|
return super.onUnbind(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******** following methods may be accessed from Activities and Receivers ************
|
||||||
|
/**
|
||||||
|
* @returns null if router is not running
|
||||||
|
*/
|
||||||
|
public RouterContext getRouterContext() {
|
||||||
|
RouterContext rv = _context;
|
||||||
|
if(rv == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(!rv.router().isAlive()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(_state != State.RUNNING
|
||||||
|
&& _state != State.ACTIVE
|
||||||
|
&& _state != State.STOPPING
|
||||||
|
&& _state != State.MANUAL_STOPPING
|
||||||
|
&& _state != State.MANUAL_QUITTING
|
||||||
|
&& _state != State.NETWORK_STOPPING) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* debug
|
||||||
|
*/
|
||||||
|
public String getState() {
|
||||||
|
return _state.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canManualStop() {
|
||||||
|
return _state == State.WAITING || _state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop and don't restart the router, but keep the service
|
||||||
|
*/
|
||||||
|
public void manualStop() {
|
||||||
|
Util.d("manualStop called"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(!canManualStop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(_state == State.STARTING) {
|
||||||
|
_starterThread.interrupt();
|
||||||
|
}
|
||||||
|
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
|
||||||
|
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
|
||||||
|
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
|
||||||
|
stopperThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the router and kill the service
|
||||||
|
*/
|
||||||
|
public void manualQuit() {
|
||||||
|
Util.d("manualQuit called"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(!canManualStop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(_state == State.STARTING) {
|
||||||
|
_starterThread.interrupt();
|
||||||
|
}
|
||||||
|
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
|
||||||
|
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
|
||||||
|
Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED));
|
||||||
|
stopperThread.start();
|
||||||
|
} else if(_state == State.WAITING) {
|
||||||
|
setState(State.MANUAL_QUITTING);
|
||||||
|
(new FinalShutdownHook()).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop and then spin waiting for a network connection, then restart
|
||||||
|
*/
|
||||||
|
public void networkStop() {
|
||||||
|
Util.d("networkStop called"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(_state == State.STARTING) {
|
||||||
|
_starterThread.interrupt();
|
||||||
|
}
|
||||||
|
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
|
||||||
|
_statusBar.replace(StatusBar.ICON_STOPPING, "Network disconnected, stopping I2P");
|
||||||
|
// don't change state, let the shutdown hook do it
|
||||||
|
Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING));
|
||||||
|
stopperThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canManualStart() {
|
||||||
|
// We can be in INIT if we restarted after crash but previous state was not RUNNING.
|
||||||
|
return _state == State.INIT || _state == State.MANUAL_STOPPED || _state == State.STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void manualStart() {
|
||||||
|
Util.d("restart called"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(!canManualStart()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
|
||||||
|
setState(State.STARTING);
|
||||||
|
_starterThread = new Thread(new Starter());
|
||||||
|
_starterThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ******** end methods accessed from Activities and Receivers ************
|
||||||
|
|
||||||
|
private static final int STATE_MSG = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our Handler used to execute operations on the main thread.
|
||||||
|
*/
|
||||||
|
private final Handler mHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case STATE_MSG:
|
||||||
|
final State state = _state;
|
||||||
|
// Broadcast to all clients the new state.
|
||||||
|
final int N = mStateCallbacks.beginBroadcast();
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
try {
|
||||||
|
mStateCallbacks.getBroadcastItem(i).stateChanged(state);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// The RemoteCallbackList will take care of removing
|
||||||
|
// the dead object for us.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mStateCallbacks.finishBroadcast();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
super.handleMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn off the status bar. Unregister the receiver. If we were running,
|
||||||
|
* fire up the Stopper thread.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
Util.d("onDestroy called"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
|
||||||
|
_handler.removeCallbacks(_updater);
|
||||||
|
_statusBar.remove();
|
||||||
|
|
||||||
|
I2PReceiver rcvr = _receiver;
|
||||||
|
if(rcvr != null) {
|
||||||
|
synchronized(rcvr) {
|
||||||
|
try {
|
||||||
|
// throws if not registered
|
||||||
|
unregisterReceiver(rcvr);
|
||||||
|
} catch(IllegalArgumentException iae) {
|
||||||
|
}
|
||||||
|
//rcvr.unbindRouter();
|
||||||
|
//_receiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(_state == State.STARTING) {
|
||||||
|
_starterThread.interrupt();
|
||||||
|
}
|
||||||
|
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
|
||||||
|
// should this be in a thread?
|
||||||
|
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
|
||||||
|
Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
|
||||||
|
stopperThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transition to the next state. If we still have a context, shut down the
|
||||||
|
* router. Turn off the status bar. Then transition to the stop state.
|
||||||
|
*/
|
||||||
|
private class Stopper implements Runnable {
|
||||||
|
|
||||||
|
private final State nextState;
|
||||||
|
private final State stopState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call holding statelock
|
||||||
|
*/
|
||||||
|
public Stopper(State next, State stop) {
|
||||||
|
nextState = next;
|
||||||
|
stopState = stop;
|
||||||
|
setState(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Util.d(MARKER + this + " stopper thread"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
RouterContext ctx = _context;
|
||||||
|
if(ctx != null) {
|
||||||
|
ctx.router().shutdown(Router.EXIT_HARD);
|
||||||
|
}
|
||||||
|
_statusBar.remove();
|
||||||
|
Util.d("********** Router shutdown complete");
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
if(_state == nextState) {
|
||||||
|
setState(stopState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stopForeground(true);
|
||||||
|
_statusBar.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First (early) hook. Update the status bar. Unregister the receiver.
|
||||||
|
*/
|
||||||
|
private class ShutdownHook implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Util.d(this + " shutdown hook"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
|
||||||
|
I2PReceiver rcvr = _receiver;
|
||||||
|
if(rcvr != null) {
|
||||||
|
synchronized(rcvr) {
|
||||||
|
try {
|
||||||
|
// throws if not registered
|
||||||
|
unregisterReceiver(rcvr);
|
||||||
|
} catch(IllegalArgumentException iae) {
|
||||||
|
}
|
||||||
|
//rcvr.unbindRouter();
|
||||||
|
//_receiver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
// null out to release the memory
|
||||||
|
_context = null;
|
||||||
|
if(_state == State.STARTING) {
|
||||||
|
_starterThread.interrupt();
|
||||||
|
}
|
||||||
|
if(_state == State.WAITING || _state == State.STARTING
|
||||||
|
|| _state == State.RUNNING || _state == State.ACTIVE) {
|
||||||
|
setState(State.STOPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Second (late) hook. Turn off the status bar. Null out the context. If we
|
||||||
|
* were stopped manually, do nothing. If we were stopped because of no
|
||||||
|
* network, start the waiter thread. If it stopped of unknown causes or from
|
||||||
|
* manualQuit(), kill the Service.
|
||||||
|
*/
|
||||||
|
private class FinalShutdownHook implements Runnable {
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Util.d(this + " final shutdown hook"
|
||||||
|
+ " Current state is: " + _state);
|
||||||
|
//I2PReceiver rcvr = _receiver;
|
||||||
|
|
||||||
|
synchronized(_stateLock) {
|
||||||
|
// null out to release the memory
|
||||||
|
_context = null;
|
||||||
|
Runtime.getRuntime().gc();
|
||||||
|
if(_state == State.STARTING) {
|
||||||
|
_starterThread.interrupt();
|
||||||
|
}
|
||||||
|
if(_state == State.MANUAL_STOPPING) {
|
||||||
|
setState(State.MANUAL_STOPPED);
|
||||||
|
} else if(_state == State.NETWORK_STOPPING) {
|
||||||
|
// start waiter handler
|
||||||
|
setState(State.WAITING);
|
||||||
|
_handler.postDelayed(new Waiter(), 10 * 1000);
|
||||||
|
} else if(_state == State.STARTING || _state == State.RUNNING
|
||||||
|
|| _state == State.ACTIVE || _state == State.STOPPING) {
|
||||||
|
Util.w(this + " died of unknown causes");
|
||||||
|
setState(State.STOPPED);
|
||||||
|
// Unregister all callbacks.
|
||||||
|
mStateCallbacks.kill();
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
} else if(_state == State.MANUAL_QUITTING) {
|
||||||
|
setState(State.MANUAL_QUITTED);
|
||||||
|
// Unregister all callbacks.
|
||||||
|
mStateCallbacks.kill();
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_statusBar.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private State getSavedState() {
|
||||||
|
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||||
|
String stateString = prefs.getString(LAST_STATE, State.INIT.toString());
|
||||||
|
try {
|
||||||
|
return State.valueOf(stateString);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return State.INIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setState(State s) {
|
||||||
|
_state = s;
|
||||||
|
saveState();
|
||||||
|
mHandler.sendEmptyMessage(STATE_MSG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return success
|
||||||
|
*/
|
||||||
|
private boolean saveState() {
|
||||||
|
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||||
|
SharedPreferences.Editor edit = prefs.edit();
|
||||||
|
edit.putString(LAST_STATE, _state.toString());
|
||||||
|
return edit.commit();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
import net.i2p.stat.Rate;
|
||||||
|
import net.i2p.stat.RateStat;
|
||||||
|
|
||||||
|
public class StatSummarizer implements Runnable {
|
||||||
|
private final RouterContext _context;
|
||||||
|
private final List<SummaryListener> _listeners;
|
||||||
|
// TODO remove static instance
|
||||||
|
private static StatSummarizer _instance;
|
||||||
|
private volatile boolean _isRunning = true;
|
||||||
|
private Thread _thread;
|
||||||
|
|
||||||
|
public StatSummarizer() {
|
||||||
|
_context = RouterContext.listContexts().get(0);
|
||||||
|
_listeners = new CopyOnWriteArrayList<SummaryListener>();
|
||||||
|
_instance = this;
|
||||||
|
_context.addShutdownTask(new Shutdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StatSummarizer instance() { return _instance; }
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
_thread = Thread.currentThread();
|
||||||
|
String specs = "";
|
||||||
|
while (_isRunning && _context.router().isAlive()) {
|
||||||
|
specs = adjustDatabases(specs);
|
||||||
|
try { Thread.sleep(60 * 1000);} catch (InterruptedException ie) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** list of SummaryListener instances */
|
||||||
|
public List<SummaryListener> getListeners() { return _listeners; }
|
||||||
|
|
||||||
|
public SummaryListener getListener(String rateName, long period) {
|
||||||
|
for (SummaryListener lsnr : _listeners) {
|
||||||
|
if (lsnr.getName().equals(rateName + "." + period))
|
||||||
|
return lsnr;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String DEFAULT_DATABASES =
|
||||||
|
"bw.sendRate.60000"
|
||||||
|
+ ",bw.recvRate.60000"
|
||||||
|
+ ",router.memoryUsed.60000"
|
||||||
|
+ ",router.activePeers.60000";
|
||||||
|
|
||||||
|
private String adjustDatabases(String oldSpecs) {
|
||||||
|
String spec = _context.getProperty("stat.summaries", DEFAULT_DATABASES);
|
||||||
|
if ( ( (spec == null) && (oldSpecs == null) ) ||
|
||||||
|
( (spec != null) && (oldSpecs != null) && (oldSpecs.equals(spec))) )
|
||||||
|
return oldSpecs;
|
||||||
|
|
||||||
|
List<Rate> old = parseSpecs(oldSpecs);
|
||||||
|
List<Rate> newSpecs = parseSpecs(spec);
|
||||||
|
|
||||||
|
// remove old ones
|
||||||
|
for (Rate r : old) {
|
||||||
|
if (!newSpecs.contains(r))
|
||||||
|
removeDb(r);
|
||||||
|
}
|
||||||
|
// add new ones
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
boolean comma = false;
|
||||||
|
for (Rate r : newSpecs) {
|
||||||
|
if (!old.contains(r))
|
||||||
|
addDb(r);
|
||||||
|
if (comma)
|
||||||
|
buf.append(',');
|
||||||
|
else
|
||||||
|
comma = true;
|
||||||
|
buf.append(r.getRateStat().getName()).append(".").append(r.getPeriod());
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeDb(Rate r) {
|
||||||
|
for (SummaryListener lsnr : _listeners) {
|
||||||
|
if (lsnr.getRate().equals(r)) {
|
||||||
|
// no iter.remove() in COWAL
|
||||||
|
_listeners.remove(lsnr);
|
||||||
|
lsnr.stopListening();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void addDb(Rate r) {
|
||||||
|
SummaryListener lsnr = new SummaryListener(r);
|
||||||
|
lsnr.startListening();
|
||||||
|
_listeners.add(lsnr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param specs statName.period,statName.period,statName.period
|
||||||
|
* @return list of Rate objects
|
||||||
|
*/
|
||||||
|
List<Rate> parseSpecs(String specs) {
|
||||||
|
StringTokenizer tok = new StringTokenizer(specs, ",");
|
||||||
|
List<Rate> rv = new ArrayList<Rate>();
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String spec = tok.nextToken();
|
||||||
|
int split = spec.lastIndexOf('.');
|
||||||
|
if ( (split <= 0) || (split + 1 >= spec.length()) )
|
||||||
|
continue;
|
||||||
|
String name = spec.substring(0, split);
|
||||||
|
String per = spec.substring(split+1);
|
||||||
|
long period = -1;
|
||||||
|
try {
|
||||||
|
period = Long.parseLong(per);
|
||||||
|
RateStat rs = _context.statManager().getRate(name);
|
||||||
|
if (rs != null) {
|
||||||
|
Rate r = rs.getRate(period);
|
||||||
|
if (r != null)
|
||||||
|
rv.add(r);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException nfe) {}
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Shutdown implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
_isRunning = false;
|
||||||
|
if (_thread != null)
|
||||||
|
_thread.interrupt();
|
||||||
|
for (SummaryListener lsnr : _listeners) {
|
||||||
|
lsnr.stopListening();
|
||||||
|
}
|
||||||
|
_listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
108
app/src/main/java/net/i2p/android/router/service/StatusBar.java
Normal file
108
app/src/main/java/net/i2p/android/router/service/StatusBar.java
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import net.i2p.android.router.MainActivity;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
|
class StatusBar {
|
||||||
|
|
||||||
|
private final Context ctx;
|
||||||
|
private final NotificationManager mNotificationManager;
|
||||||
|
private final NotificationCompat.Builder mNotifyBuilder;
|
||||||
|
private Notification mNotif;
|
||||||
|
|
||||||
|
private static final int ID = 1337;
|
||||||
|
|
||||||
|
public static final int ICON_STARTING = R.drawable.ic_stat_router_starting;
|
||||||
|
public static final int ICON_RUNNING = R.drawable.ic_stat_router_running;
|
||||||
|
public static final int ICON_ACTIVE = R.drawable.ic_stat_router_active;
|
||||||
|
public static final int ICON_STOPPING = R.drawable.ic_stat_router_stopping;
|
||||||
|
public static final int ICON_SHUTTING_DOWN = R.drawable.ic_stat_router_shutting_down;
|
||||||
|
public static final int ICON_WAITING_NETWORK = R.drawable.ic_stat_router_waiting_network;
|
||||||
|
|
||||||
|
StatusBar(Context cx) {
|
||||||
|
ctx = cx;
|
||||||
|
mNotificationManager = (NotificationManager) ctx.getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE);
|
||||||
|
Thread.currentThread().setUncaughtExceptionHandler(
|
||||||
|
new CrashHandler(mNotificationManager));
|
||||||
|
|
||||||
|
int icon = ICON_STARTING;
|
||||||
|
// won't be shown if replace() is called
|
||||||
|
String text = "Starting I2P";
|
||||||
|
|
||||||
|
mNotifyBuilder = new NotificationCompat.Builder(ctx)
|
||||||
|
.setContentText(text)
|
||||||
|
.setSmallIcon(icon)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setOnlyAlertOnce(true);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctx, MainActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
mNotifyBuilder.setContentIntent(pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replace(int icon, String text) {
|
||||||
|
mNotifyBuilder.setSmallIcon(icon)
|
||||||
|
.setStyle(null)
|
||||||
|
.setTicker(text);
|
||||||
|
update(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(String text) {
|
||||||
|
String title = "I2P Status";
|
||||||
|
update(title, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(String title, String text, String bigText) {
|
||||||
|
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle()
|
||||||
|
.bigText(bigText));
|
||||||
|
update(title, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(String title, String text) {
|
||||||
|
mNotifyBuilder.setContentTitle(title)
|
||||||
|
.setContentText(text);
|
||||||
|
mNotif = mNotifyBuilder.build();
|
||||||
|
mNotificationManager.notify(ID, mNotif);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
mNotificationManager.cancel(ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://stackoverflow.com/questions/4028742/how-to-clear-a-notification-if-activity-crashes
|
||||||
|
*/
|
||||||
|
private static class CrashHandler implements Thread.UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
private final Thread.UncaughtExceptionHandler defaultUEH;
|
||||||
|
private final NotificationManager mgr;
|
||||||
|
|
||||||
|
public CrashHandler(NotificationManager nMgr) {
|
||||||
|
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
mgr = nMgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
if (mgr != null) {
|
||||||
|
try {
|
||||||
|
mgr.cancel(ID);
|
||||||
|
} catch (Throwable ex) {}
|
||||||
|
}
|
||||||
|
System.err.println("In CrashHandler " + e);
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
defaultUEH.uncaughtException(t, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Notification getNote() {
|
||||||
|
return mNotif;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package net.i2p.android.router.service;
|
||||||
|
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.Observer;
|
||||||
|
|
||||||
|
import com.androidplot.xy.SimpleXYSeries;
|
||||||
|
import com.androidplot.xy.XYSeries;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.stat.Rate;
|
||||||
|
import net.i2p.stat.RateStat;
|
||||||
|
import net.i2p.stat.RateSummaryListener;
|
||||||
|
|
||||||
|
public class SummaryListener implements RateSummaryListener {
|
||||||
|
public static final int HISTORY_SIZE = 30;
|
||||||
|
|
||||||
|
private final I2PAppContext _context;
|
||||||
|
private final Rate _rate;
|
||||||
|
private String _name;
|
||||||
|
private SimpleXYSeries _series;
|
||||||
|
private MyObservable _notifier;
|
||||||
|
|
||||||
|
public SummaryListener(Rate r) {
|
||||||
|
_context = I2PAppContext.getGlobalContext();
|
||||||
|
_rate = r;
|
||||||
|
_notifier = new MyObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// encapsulates management of the observers watching this rate for update events:
|
||||||
|
class MyObservable extends Observable {
|
||||||
|
@Override
|
||||||
|
public void notifyObservers() {
|
||||||
|
setChanged();
|
||||||
|
super.notifyObservers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addObserver(Observer observer) {
|
||||||
|
_notifier.addObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeObserver(Observer observer) {
|
||||||
|
_notifier.deleteObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(double totalValue, long eventCount, double totalEventTime,
|
||||||
|
long period) {
|
||||||
|
long now = now();
|
||||||
|
long when = now / 1000;
|
||||||
|
double val = eventCount > 0 ? (totalValue / eventCount) : 0d;
|
||||||
|
|
||||||
|
if (_series.size() > HISTORY_SIZE)
|
||||||
|
_series.removeFirst();
|
||||||
|
|
||||||
|
_series.addLast(when, val);
|
||||||
|
|
||||||
|
_notifier.notifyObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rate getRate() { return _rate; }
|
||||||
|
|
||||||
|
public String getName() { return _name; }
|
||||||
|
|
||||||
|
public XYSeries getSeries() { return _series; }
|
||||||
|
|
||||||
|
long now() { return _context.clock().now(); }
|
||||||
|
|
||||||
|
public void startListening() {
|
||||||
|
RateStat rs = _rate.getRateStat();
|
||||||
|
long period = _rate.getPeriod();
|
||||||
|
_name = rs.getName() + "." + period;
|
||||||
|
_series = new SimpleXYSeries(_name);
|
||||||
|
_series.useImplicitXVals();
|
||||||
|
_rate.setSummaryListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopListening() {
|
||||||
|
_rate.setSummaryListener(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package net.i2p.android.router.stats;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.service.RouterService;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class PeersActivity extends I2PActivityBase {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
// Start with the base view
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
PeersFragment f = new PeersFragment();
|
||||||
|
f.setArguments(getIntent().getExtras());
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, f).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not bound by the time onResume() is called, so we have to do it here.
|
||||||
|
* If it is bound we update twice.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onRouterBind(RouterService svc) {
|
||||||
|
PeersFragment f = (PeersFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||||
|
f.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
PeersFragment f = (PeersFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||||
|
if (!f.onBackPressed())
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package net.i2p.android.router.stats;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.web.I2PWebViewClient;
|
||||||
|
import net.i2p.router.CommSystemFacade;
|
||||||
|
|
||||||
|
public class PeersFragment extends I2PFragmentBase {
|
||||||
|
|
||||||
|
private I2PWebViewClient _wvClient;
|
||||||
|
|
||||||
|
// TODO add some inline style
|
||||||
|
private static final String HEADER = "<html><head></head><body>";
|
||||||
|
private static final String FOOTER = "</body></html>";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.peers, container, false);
|
||||||
|
WebView wv = (WebView) v.findViewById(R.id.peers_webview);
|
||||||
|
wv.getSettings().setLoadsImagesAutomatically(true); // was false
|
||||||
|
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||||
|
wv.getSettings().setUseWideViewPort(true);
|
||||||
|
_wvClient = new I2PWebViewClient();
|
||||||
|
wv.setWebViewClient(_wvClient);
|
||||||
|
wv.getSettings().setBuiltInZoomControls(true);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
WebView wv = (WebView) getActivity().findViewById(R.id.peers_webview);
|
||||||
|
wv.clearHistory(); // fixes having to hit back.
|
||||||
|
CommSystemFacade comm = getCommSystem();
|
||||||
|
String data;
|
||||||
|
if (comm != null) {
|
||||||
|
StringWriter out = new StringWriter(32*1024);
|
||||||
|
out.append(HEADER);
|
||||||
|
try {
|
||||||
|
comm.renderStatusHTML(out, "http://thiswontwork.i2p/peers", 0);
|
||||||
|
out.append(FOOTER);
|
||||||
|
data = out.toString().replaceAll("/themes", "themes");
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
data = HEADER + "Error: " + ioe + FOOTER;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = HEADER + "No peer data available. The router is not running." + FOOTER;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// wv.loadData(data, "text/html", "UTF-8");
|
||||||
|
// figure out a way to get /themes/console/images/outbound.png to load
|
||||||
|
// String url = "file://" + _myDir + "/docs/";
|
||||||
|
String url = "file:///android_asset/";
|
||||||
|
wv.loadDataWithBaseURL(url, data, "text/html", "UTF-8", url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onBackPressed() {
|
||||||
|
WebView wv = (WebView) getActivity().findViewById(R.id.peers_webview);
|
||||||
|
_wvClient.cancelAll();
|
||||||
|
wv.stopLoading();
|
||||||
|
|
||||||
|
// We do not want to go back, or keep history... There is no need to.
|
||||||
|
// What we DO want to do is exit!
|
||||||
|
//if (wv.canGoBack()) {
|
||||||
|
// wv.goBack();
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_web_actions, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_reload:
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
package net.i2p.android.router.stats;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.SettingsActivity;
|
||||||
|
import net.i2p.android.router.service.StatSummarizer;
|
||||||
|
import net.i2p.android.router.service.SummaryListener;
|
||||||
|
import net.i2p.stat.Rate;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.SpinnerAdapter;
|
||||||
|
|
||||||
|
public class RateGraphActivity extends I2PActivityBase {
|
||||||
|
private static final String SELECTED_RATE = "selected_rate";
|
||||||
|
|
||||||
|
private boolean mFinishOnResume;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||||
|
|
||||||
|
if (StatSummarizer.instance() != null) {
|
||||||
|
// Get the rates currently being graphed
|
||||||
|
List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
|
||||||
|
TreeSet<SummaryListener> ordered = new TreeSet<SummaryListener>(new AlphaComparator());
|
||||||
|
ordered.addAll(listeners);
|
||||||
|
|
||||||
|
if (ordered.size() > 0) {
|
||||||
|
// Extract the rates and periods
|
||||||
|
final String[] mRates = new String[ordered.size()];
|
||||||
|
final long[] mPeriods = new long[ordered.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (SummaryListener listener : ordered) {
|
||||||
|
Rate r = listener.getRate();
|
||||||
|
mRates[i] = r.getRateStat().getName();
|
||||||
|
mPeriods[i] = r.getPeriod();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up action bar for drop-down list
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||||
|
|
||||||
|
SpinnerAdapter mSpinnerAdapter = new ArrayAdapter<String>(this,
|
||||||
|
android.R.layout.simple_spinner_dropdown_item, mRates);
|
||||||
|
|
||||||
|
ActionBar.OnNavigationListener mNavigationListener = new ActionBar.OnNavigationListener() {
|
||||||
|
String[] rates = mRates;
|
||||||
|
long[] periods = mPeriods;
|
||||||
|
|
||||||
|
public boolean onNavigationItemSelected(int position, long itemId) {
|
||||||
|
String rateName = rates[position];
|
||||||
|
long period = periods[position];
|
||||||
|
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.main_fragment, f, rates[position]).commit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationListener);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
int selected = savedInstanceState.getInt(SELECTED_RATE);
|
||||||
|
actionBar.setSelectedNavigationItem(selected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DialogFragment df = new DialogFragment() {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setMessage(R.string.no_graphs_configured)
|
||||||
|
.setPositiveButton(R.string.configure_graphs, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
mFinishOnResume = true;
|
||||||
|
Intent i = new Intent(RateGraphActivity.this, SettingsActivity.class);
|
||||||
|
// Navigation to a sub-category doesn't seem to work yet
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
i.setAction(SettingsActivity.ACTION_PREFS_GRAPHS);
|
||||||
|
} else {
|
||||||
|
i.putExtra("settings", "graphs");
|
||||||
|
}
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int i) {
|
||||||
|
dialog.cancel();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setCancelable(false);
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
df.show(getSupportFragmentManager(), "nographs");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DialogFragment df = new DialogFragment() {
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setMessage(R.string.graphs_not_ready)
|
||||||
|
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setCancelable(false);
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
df.show(getSupportFragmentManager(), "graphsnotready");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
if (mFinishOnResume) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt(SELECTED_RATE,
|
||||||
|
getSupportActionBar().getSelectedNavigationIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AlphaComparator implements Comparator<SummaryListener> {
|
||||||
|
public int compare(SummaryListener l, SummaryListener r) {
|
||||||
|
String lName = l.getRate().getRateStat().getName();
|
||||||
|
String rName = r.getRate().getRateStat().getName();
|
||||||
|
int rv = lName.compareTo(rName);
|
||||||
|
if (rv != 0)
|
||||||
|
return rv;
|
||||||
|
return (int) (l.getRate().getPeriod() - r.getRate().getPeriod());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,160 @@
|
|||||||
|
package net.i2p.android.router.stats;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.FieldPosition;
|
||||||
|
import java.text.Format;
|
||||||
|
import java.text.ParsePosition;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.Observer;
|
||||||
|
import com.androidplot.Plot;
|
||||||
|
import com.androidplot.xy.BoundaryMode;
|
||||||
|
import com.androidplot.xy.LineAndPointFormatter;
|
||||||
|
import com.androidplot.xy.XYPlot;
|
||||||
|
import com.androidplot.xy.XYSeries;
|
||||||
|
import com.androidplot.xy.XYStepMode;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.router.service.StatSummarizer;
|
||||||
|
import net.i2p.android.router.service.SummaryListener;
|
||||||
|
import net.i2p.android.router.util.Util;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public class RateGraphFragment extends I2PFragmentBase {
|
||||||
|
// redraws a plot whenever an update is received:
|
||||||
|
private class MyPlotUpdater implements Observer {
|
||||||
|
Plot plot;
|
||||||
|
|
||||||
|
public MyPlotUpdater(Plot plot) {
|
||||||
|
this.plot = plot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
Util.d("Redrawing plot");
|
||||||
|
plot.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String RATE_NAME = "rate_name";
|
||||||
|
public static final String RATE_PERIOD = "rate_period";
|
||||||
|
|
||||||
|
private Handler _handler;
|
||||||
|
private SetupTask _setupTask;
|
||||||
|
private SummaryListener _listener;
|
||||||
|
private XYPlot _ratePlot;
|
||||||
|
private MyPlotUpdater _plotUpdater;
|
||||||
|
|
||||||
|
public static RateGraphFragment newInstance(String name, long period) {
|
||||||
|
RateGraphFragment f = new RateGraphFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(RATE_NAME, name);
|
||||||
|
args.putLong(RATE_PERIOD, period);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
_handler = new Handler();
|
||||||
|
_setupTask = new SetupTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_graph, container, false);
|
||||||
|
|
||||||
|
_ratePlot = (XYPlot) v.findViewById(R.id.rate_stat_plot);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
_handler.removeCallbacks(_setupTask);
|
||||||
|
_handler.postDelayed(_setupTask, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (_listener != null && _plotUpdater != null) {
|
||||||
|
Util.d("Removing plot updater from listener");
|
||||||
|
_listener.removeObserver(_plotUpdater);
|
||||||
|
}
|
||||||
|
_handler.removeCallbacks(_setupTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SetupTask implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
String rateName = getArguments().getString(RATE_NAME);
|
||||||
|
long period = getArguments().getLong(RATE_PERIOD);
|
||||||
|
|
||||||
|
Util.d("Setting up " + rateName + "." + period);
|
||||||
|
if (StatSummarizer.instance() == null) {
|
||||||
|
Util.d("StatSummarizer is null, delaying setup");
|
||||||
|
_handler.postDelayed(this, 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_listener = StatSummarizer.instance().getListener(rateName, period);
|
||||||
|
if (_listener == null) {
|
||||||
|
Util.d("Listener is null, delaying setup");
|
||||||
|
_handler.postDelayed(this, 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XYSeries rateSeries = _listener.getSeries();
|
||||||
|
|
||||||
|
_plotUpdater = new MyPlotUpdater(_ratePlot);
|
||||||
|
|
||||||
|
_ratePlot.addSeries(rateSeries, new LineAndPointFormatter(Color.rgb(0, 0, 0), null, Color.rgb(0, 80, 0), null));
|
||||||
|
|
||||||
|
Util.d("Adding plot updater to listener");
|
||||||
|
_listener.addObserver(_plotUpdater);
|
||||||
|
|
||||||
|
_ratePlot.setDomainStepMode(XYStepMode.SUBDIVIDE);
|
||||||
|
_ratePlot.setDomainStepValue(SummaryListener.HISTORY_SIZE);
|
||||||
|
|
||||||
|
// thin out domain/range tick labels so they dont overlap each other:
|
||||||
|
_ratePlot.setTicksPerDomainLabel(5);
|
||||||
|
_ratePlot.setTicksPerRangeLabel(3);
|
||||||
|
|
||||||
|
_ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
|
||||||
|
|
||||||
|
_ratePlot.setRangeValueFormat(new Format() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuffer format(Object obj, StringBuffer toAppendTo,
|
||||||
|
FieldPosition pos) {
|
||||||
|
double val = ((Number) obj).doubleValue();
|
||||||
|
if (val >= 10 * 1000 * 1000)
|
||||||
|
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
|
||||||
|
else if (val >= 8 * 100 * 1000)
|
||||||
|
return new DecimalFormat("0.0 M").format(val / (1000 * 1000), toAppendTo, pos);
|
||||||
|
else if (val >= 10 * 1000)
|
||||||
|
return new DecimalFormat("0 k").format(val / (1000), toAppendTo, pos);
|
||||||
|
else if (val >= 8 * 100)
|
||||||
|
return new DecimalFormat("0.0 k").format(val / (1000), toAppendTo, pos);
|
||||||
|
else
|
||||||
|
return new DecimalFormat("0").format(val, toAppendTo, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseObject(String source, ParsePosition pos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Util.d("Redrawing plot");
|
||||||
|
_ratePlot.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,9 @@ import android.content.ContentResolver;
|
|||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -15,9 +14,7 @@ import java.util.Comparator;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import net.i2p.android.router.provider.CacheProvider;
|
import net.i2p.android.router.provider.CacheProvider;
|
||||||
import net.i2p.android.router.util.Util;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A least recently used cache with a max number of entries
|
* A least recently used cache with a max number of entries
|
||||||
@ -62,7 +59,7 @@ public class AppCache {
|
|||||||
private AppCache(Context ctx) {
|
private AppCache(Context ctx) {
|
||||||
_cacheDir = new File(ctx.getCacheDir(), DIR_NAME);
|
_cacheDir = new File(ctx.getCacheDir(), DIR_NAME);
|
||||||
_cacheDir.mkdir();
|
_cacheDir.mkdir();
|
||||||
Util.e("AppCache cache dir " + _cacheDir);
|
Util.d("AppCache cache dir " + _cacheDir);
|
||||||
_resolver = ctx.getContentResolver();
|
_resolver = ctx.getContentResolver();
|
||||||
_cache = new LHM(MAX_FILES);
|
_cache = new LHM(MAX_FILES);
|
||||||
initialize();
|
initialize();
|
||||||
@ -122,7 +119,7 @@ public class AppCache {
|
|||||||
public Uri getCacheUri(Uri key) {
|
public Uri getCacheUri(Uri key) {
|
||||||
int hash = toHash(key);
|
int hash = toHash(key);
|
||||||
// poke the LRU
|
// poke the LRU
|
||||||
Object present = null;
|
Object present;
|
||||||
synchronized(_cache) {
|
synchronized(_cache) {
|
||||||
present = _cache.get(Integer.valueOf(hash));
|
present = _cache.get(Integer.valueOf(hash));
|
||||||
}
|
}
|
||||||
@ -145,9 +142,9 @@ public class AppCache {
|
|||||||
|
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
_totalSize = 0;
|
_totalSize = 0;
|
||||||
List<File> fileList = new ArrayList(MAX_FILES);
|
List<File> fileList = new ArrayList<File>(MAX_FILES);
|
||||||
long total = enumerate(_cacheDir, fileList);
|
long total = enumerate(_cacheDir, fileList);
|
||||||
Util.e("AppCache found " + fileList.size() + " files totalling " + total + " bytes");
|
Util.d("AppCache found " + fileList.size() + " files totalling " + total + " bytes");
|
||||||
Collections.sort(fileList, new FileComparator());
|
Collections.sort(fileList, new FileComparator());
|
||||||
// oldest first, delete if too big or too old, else add to LHM
|
// oldest first, delete if too big or too old, else add to LHM
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
@ -160,7 +157,7 @@ public class AppCache {
|
|||||||
// TODO insertContent
|
// TODO insertContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Util.e("after init " + _cache.size() + " files totalling " + total + " bytes");
|
Util.d("after init " + _cache.size() + " files totalling " + total + " bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** oldest first */
|
/** oldest first */
|
||||||
@ -201,7 +198,7 @@ public class AppCache {
|
|||||||
_cache.put(Integer.valueOf(hash), DUMMY);
|
_cache.put(Integer.valueOf(hash), DUMMY);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
Util.e("Huh bad file?" + iae);
|
Util.d("Huh bad file?" + iae);
|
||||||
f.delete();
|
f.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,6 +282,7 @@ public class AppCache {
|
|||||||
* on remove.
|
* on remove.
|
||||||
*/
|
*/
|
||||||
private static class LHM extends LinkedHashMap<Integer, Object> {
|
private static class LHM extends LinkedHashMap<Integer, Object> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
private final int _max;
|
private final int _max;
|
||||||
|
|
||||||
public LHM(int max) {
|
public LHM(int max) {
|
||||||
@ -312,7 +310,7 @@ public class AppCache {
|
|||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
_totalSize -= f.length();
|
_totalSize -= f.length();
|
||||||
f.delete();
|
f.delete();
|
||||||
Util.e("AppCache deleted file " + f);
|
Util.d("AppCache deleted file " + f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
@ -0,0 +1,51 @@
|
|||||||
|
package net.i2p.android.router.util;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import android.util.Patterns;
|
||||||
|
|
||||||
|
public class I2Patterns {
|
||||||
|
/**
|
||||||
|
* The double-parentheses are needed because the included
|
||||||
|
* pattern has an additional closing parenthesis.
|
||||||
|
*/
|
||||||
|
public static final String I2P_TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
|
||||||
|
"(?:("
|
||||||
|
+ Patterns.TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
|
||||||
|
+ "|i2p))";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression pattern to match most part of RFC 3987
|
||||||
|
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
|
||||||
|
* added.
|
||||||
|
* Copied from android.util.Patterns
|
||||||
|
*/
|
||||||
|
public static final Pattern I2P_WEB_URL = Pattern.compile(
|
||||||
|
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
|
||||||
|
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
|
||||||
|
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
|
||||||
|
+ "((?:(?:[" + Patterns.GOOD_IRI_CHAR + "][" + Patterns.GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
|
||||||
|
+ I2P_TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
|
||||||
|
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
|
||||||
|
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
|
||||||
|
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
|
||||||
|
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
|
||||||
|
+ "|[1-9][0-9]|[0-9])))"
|
||||||
|
+ "(?:\\:\\d{1,5})?)" // plus option port number
|
||||||
|
+ "(\\/(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
|
||||||
|
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
|
||||||
|
+ "(?:\\b|$)"); // and finally, a word boundary or end of
|
||||||
|
// input. This is to stop foo.sure from
|
||||||
|
// matching as foo.su
|
||||||
|
|
||||||
|
public static final Pattern IRC_URL = Pattern.compile(
|
||||||
|
"(irc:\\/\\/127\\.0\\.0\\.1\\:\\d{1,5})"
|
||||||
|
+ "(\\/(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
|
||||||
|
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
|
||||||
|
+ "(?:\\b|$)"); // and finally, a word boundary or end of
|
||||||
|
// input. This is to stop foo.sure from
|
||||||
|
// matching as foo.su
|
||||||
|
|
||||||
|
private I2Patterns() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package net.i2p.android.router.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
public class LongToggleButton extends ToggleButton {
|
||||||
|
public LongToggleButton(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongToggleButton(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongToggleButton(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean performClick() {
|
||||||
|
/* Cancel out toggle */
|
||||||
|
toggle();
|
||||||
|
return super.performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean performLongClick() {
|
||||||
|
/* When clicked, toggle the state */
|
||||||
|
toggle();
|
||||||
|
return super.performLongClick();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
package net.i2p.android.router.util;
|
||||||
|
|
||||||
|
import java.net.IDN;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import net.i2p.android.wizard.model.Page;
|
||||||
|
import net.i2p.client.naming.NamingService;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
import net.i2p.router.RouterContext;
|
||||||
|
|
||||||
|
public class NamingServiceUtil {
|
||||||
|
private static final String DEFAULT_NS = "BlockfileNamingService";
|
||||||
|
|
||||||
|
public static boolean addFromWizard(
|
||||||
|
Context ctx, NamingService ns, Bundle data, boolean replace) {
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
// Get the Bundle keys
|
||||||
|
Resources res = ctx.getResources();
|
||||||
|
|
||||||
|
String kHostName = res.getString(R.string.addressbook_add_wizard_k_name);
|
||||||
|
String kDest = res.getString(R.string.addressbook_add_wizard_k_destination);
|
||||||
|
|
||||||
|
String hostName = data.getBundle(kHostName).getString(Page.SIMPLE_DATA_KEY);
|
||||||
|
String host = toASCII(res, hostName); // Already validated, won't throw IAE
|
||||||
|
String displayHost = host.equals(hostName) ? hostName :
|
||||||
|
hostName + " (" + host + ')';
|
||||||
|
|
||||||
|
String destB64 = data.getBundle(kDest).getString(Page.SIMPLE_DATA_KEY);
|
||||||
|
Destination dest = new Destination();
|
||||||
|
try {
|
||||||
|
dest.fromBase64(destB64);
|
||||||
|
} catch (DataFormatException e) {} // Already validated
|
||||||
|
|
||||||
|
// Check if already in addressbook
|
||||||
|
Destination oldDest = ns.lookup(host);
|
||||||
|
if (oldDest != null) {
|
||||||
|
if (destB64.equals(oldDest.toBase64()))
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
"Host name " + displayHost + " is already in address book, unchanged.",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
else if (!replace)
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
"Host name " + displayHost + " is already in address book with a different Destination.",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
// Put the new host name
|
||||||
|
success = ns.put(host, dest);
|
||||||
|
if (!success)
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
"Failed to add Destination " + displayHost + " to naming service " + ns.getName(),
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the NamingService for the current file name, or the root NamingService */
|
||||||
|
public static NamingService getNamingService(RouterContext ctx, String book)
|
||||||
|
{
|
||||||
|
NamingService root = ctx.namingService();
|
||||||
|
NamingService rv = searchNamingService(root, book);
|
||||||
|
return rv != null ? rv : root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** depth-first search */
|
||||||
|
private static NamingService searchNamingService(NamingService ns, String srch)
|
||||||
|
{
|
||||||
|
String name = ns.getName();
|
||||||
|
if (name.equals(srch) || basename(name).equals(srch) || name.equals(DEFAULT_NS))
|
||||||
|
return ns;
|
||||||
|
List<NamingService> list = ns.getNamingServices();
|
||||||
|
if (list != null) {
|
||||||
|
for (NamingService nss : list) {
|
||||||
|
NamingService rv = searchNamingService(nss, srch);
|
||||||
|
if (rv != null)
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String basename(String filename) {
|
||||||
|
int slash = filename.lastIndexOf('/');
|
||||||
|
if (slash >= 0)
|
||||||
|
filename = filename.substring(slash + 1);
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final char DOT = '.';
|
||||||
|
private static final char DOT2 = 0x3002;
|
||||||
|
private static final char DOT3 = 0xFF0E;
|
||||||
|
private static final char DOT4 = 0xFF61;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: java.net.IDN and RFC 3940
|
||||||
|
* @param host will be converted to lower case
|
||||||
|
* @return name converted to lower case and punycoded if necessary
|
||||||
|
* @throws IAE on various errors or if IDN is needed but not available
|
||||||
|
* @since 0.8.7
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
static String toASCII(Resources res, String host) throws IllegalArgumentException {
|
||||||
|
host = host.toLowerCase(Locale.US);
|
||||||
|
|
||||||
|
boolean needsIDN = false;
|
||||||
|
// Here we do easy checks and throw translated exceptions.
|
||||||
|
// We do checks on the whole host name, not on each "label", so
|
||||||
|
// we allow '.', and some untranslated errors will be thrown by IDN.toASCII()
|
||||||
|
for (int i = 0; i < host.length(); i++) {
|
||||||
|
char c = host.charAt(i);
|
||||||
|
if (c <= 0x2c ||
|
||||||
|
c == 0x2f ||
|
||||||
|
c >= 0x3a && c <= 0x40 ||
|
||||||
|
c >= 0x5b && c <= 0x60 ||
|
||||||
|
c >= 0x7b && c <= 0x7f) {
|
||||||
|
String bad = "\"" + c + "\" (0x" + Integer.toHexString(c) + ')';
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_illegal_char, host, bad));
|
||||||
|
}
|
||||||
|
if (c == DOT2)
|
||||||
|
host = host.replace(DOT2, DOT);
|
||||||
|
else if (c == DOT3)
|
||||||
|
host = host.replace(DOT3, DOT);
|
||||||
|
else if (c == DOT4)
|
||||||
|
host = host.replace(DOT4, DOT);
|
||||||
|
else if (c > 0x7f)
|
||||||
|
needsIDN = true;
|
||||||
|
}
|
||||||
|
if (host.startsWith("-"))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_cannot_start_with, "-"));
|
||||||
|
if (host.startsWith("."))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_cannot_start_with, "."));
|
||||||
|
if (host.endsWith("-"))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_cannot_end_with, "-"));
|
||||||
|
if (host.endsWith("."))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_cannot_end_with, "."));
|
||||||
|
if (needsIDN) {
|
||||||
|
if (host.startsWith("xn--"))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_cannot_start_with, "xn--"));
|
||||||
|
if (host.contains(".xn--"))
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_cannot_contain, ".xn--"));
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
|
||||||
|
return IDN.toASCII(host, IDN.ALLOW_UNASSIGNED);
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
res.getString(R.string.nsu_iae_requires_conversion, host));
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.i2p.android.router.util;
|
||||||
|
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
public class Notifications {
|
||||||
|
private final Context mCtx;
|
||||||
|
private final NotificationManager mNotificationManager;
|
||||||
|
|
||||||
|
public static final int ICON = R.drawable.ic_stat_router_active;
|
||||||
|
|
||||||
|
public Notifications(Context ctx) {
|
||||||
|
mCtx = ctx;
|
||||||
|
mNotificationManager = (NotificationManager) ctx.getSystemService(
|
||||||
|
Context.NOTIFICATION_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notify(String title, String text) {
|
||||||
|
notify(title, text, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notify(String title, String text, Class<?> c) {
|
||||||
|
NotificationCompat.Builder b =
|
||||||
|
new NotificationCompat.Builder(mCtx)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(text)
|
||||||
|
.setSmallIcon(ICON)
|
||||||
|
.setAutoCancel(true);
|
||||||
|
|
||||||
|
if (c != null) {
|
||||||
|
Intent intent = new Intent(mCtx, c);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(mCtx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
b.setContentIntent(pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
mNotificationManager.notify(7175, b.build());
|
||||||
|
}
|
||||||
|
}
|
180
app/src/main/java/net/i2p/android/router/util/Util.java
Normal file
180
app/src/main/java/net/i2p/android/router/util/Util.java
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package net.i2p.android.router.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.util.OrderedProperties;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public abstract class Util {
|
||||||
|
private static final boolean _isEmulator = Build.MODEL.equals("sdk");
|
||||||
|
|
||||||
|
public static String getOurVersion(Context ctx) {
|
||||||
|
PackageManager pm = ctx.getPackageManager();
|
||||||
|
String us = ctx.getPackageName();
|
||||||
|
try {
|
||||||
|
PackageInfo pi = pm.getPackageInfo(us, 0);
|
||||||
|
//System.err.println("VersionCode" + ": " + pi.versionCode);
|
||||||
|
// http://doandroids.com/blogs/2010/6/10/android-classloader-dynamic-loading-of/
|
||||||
|
//_apkPath = pm.getApplicationInfo(us, 0).sourceDir;
|
||||||
|
//System.err.println("APK Path" + ": " + _apkPath);
|
||||||
|
if (pi.versionName != null)
|
||||||
|
return pi.versionName;
|
||||||
|
} catch (Exception e) {}
|
||||||
|
return "??";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isConnected(Context ctx) {
|
||||||
|
// emulator always returns null NetworkInfo
|
||||||
|
if (_isEmulator)
|
||||||
|
return true;
|
||||||
|
NetworkInfo current = getNetworkInfo(ctx);
|
||||||
|
return current != null && current.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NetworkInfo getNetworkInfo(Context ctx) {
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo current = cm.getActiveNetworkInfo();
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String ANDROID_TAG = "I2P";
|
||||||
|
|
||||||
|
public static void e(String m) {
|
||||||
|
e(m, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log to the context logger if available (which goes to the console buffer
|
||||||
|
* and to logcat), else just to logcat.
|
||||||
|
*/
|
||||||
|
public static void e(String m, Throwable t) {
|
||||||
|
I2PAppContext ctx = I2PAppContext.getCurrentContext();
|
||||||
|
if (ctx != null)
|
||||||
|
ctx.logManager().getLog(Util.class).error(m, t);
|
||||||
|
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.ERROR)) {
|
||||||
|
if (t != null)
|
||||||
|
android.util.Log.e(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
|
||||||
|
else
|
||||||
|
android.util.Log.e(ANDROID_TAG, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void w(String m) {
|
||||||
|
w(m, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void w(String m, Throwable t) {
|
||||||
|
I2PAppContext ctx = I2PAppContext.getCurrentContext();
|
||||||
|
if (ctx != null)
|
||||||
|
ctx.logManager().getLog(Util.class).warn(m, t);
|
||||||
|
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.WARN)) {
|
||||||
|
if (t != null)
|
||||||
|
android.util.Log.w(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
|
||||||
|
else
|
||||||
|
android.util.Log.w(ANDROID_TAG, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void i(String m) {
|
||||||
|
i(m, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void i(String m, Throwable t) {
|
||||||
|
I2PAppContext ctx = I2PAppContext.getCurrentContext();
|
||||||
|
if (ctx != null)
|
||||||
|
ctx.logManager().getLog(Util.class).info(m, t);
|
||||||
|
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.INFO)) {
|
||||||
|
if (t != null)
|
||||||
|
android.util.Log.i(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
|
||||||
|
else
|
||||||
|
android.util.Log.i(ANDROID_TAG, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void d(String m) {
|
||||||
|
d(m, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(String m, Throwable t) {
|
||||||
|
I2PAppContext ctx = I2PAppContext.getCurrentContext();
|
||||||
|
if (ctx != null)
|
||||||
|
ctx.logManager().getLog(Util.class).debug(m, t);
|
||||||
|
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.DEBUG)) {
|
||||||
|
if (t != null)
|
||||||
|
android.util.Log.d(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
|
||||||
|
else
|
||||||
|
android.util.Log.d(ANDROID_TAG, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Properties> getPropertiesFromPreferences(Context context) {
|
||||||
|
List<Properties> pList = new ArrayList<Properties>();
|
||||||
|
|
||||||
|
// Copy prefs
|
||||||
|
Properties routerProps = new OrderedProperties();
|
||||||
|
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
|
// List to store stats for graphing
|
||||||
|
List<String> statSummaries = new ArrayList<String>();
|
||||||
|
|
||||||
|
// List to store Log settings
|
||||||
|
Properties logSettings = new OrderedProperties();
|
||||||
|
|
||||||
|
Map<String, ?> all = preferences.getAll();
|
||||||
|
Iterator<String> iterator = all.keySet().iterator();
|
||||||
|
// get values from the Map and make them strings.
|
||||||
|
// This loop avoids needing to convert each one, or even know it's type, or if it exists yet.
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
String x = iterator.next();
|
||||||
|
if ( x.startsWith("i2pandroid.")) // Skip over UI-related I2P Android settings
|
||||||
|
continue;
|
||||||
|
else if ( x.startsWith("stat.summaries.")) {
|
||||||
|
String stat = x.substring("stat.summaries.".length());
|
||||||
|
String checked = all.get(x).toString();
|
||||||
|
if (checked.equals("true")) {
|
||||||
|
statSummaries.add(stat);
|
||||||
|
}
|
||||||
|
} else if ( x.startsWith("logger.")) {
|
||||||
|
logSettings.put(x, all.get(x).toString());
|
||||||
|
} else if (
|
||||||
|
x.equals("router.hiddenMode") ||
|
||||||
|
x.equals("i2cp.disableInterface")) {
|
||||||
|
// special exception, we must invert the bool for these properties only.
|
||||||
|
String string = all.get(x).toString();
|
||||||
|
String inverted = Boolean.toString(!Boolean.parseBoolean(string));
|
||||||
|
routerProps.setProperty(x, inverted);
|
||||||
|
} else {
|
||||||
|
String string = all.get(x).toString();
|
||||||
|
routerProps.setProperty(x, string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statSummaries.isEmpty()) {
|
||||||
|
routerProps.setProperty("stat.summaries", "");
|
||||||
|
} else {
|
||||||
|
Iterator<String> iter = statSummaries.iterator();
|
||||||
|
StringBuilder buf = new StringBuilder(iter.next());
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
buf.append(",").append(iter.next());
|
||||||
|
}
|
||||||
|
routerProps.setProperty("stat.summaries", buf.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
pList.add(routerProps);
|
||||||
|
pList.add(logSettings);
|
||||||
|
|
||||||
|
return pList;
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,22 @@
|
|||||||
package net.i2p.android.router.activity;
|
package net.i2p.android.router.web;
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.webkit.HttpAuthHandler;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
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 net.i2p.android.apps.EepGetFetcher;
|
import net.i2p.android.apps.EepGetFetcher;
|
||||||
import net.i2p.android.router.provider.CacheProvider;
|
import net.i2p.android.router.provider.CacheProvider;
|
||||||
import net.i2p.android.router.util.AppCache;
|
import net.i2p.android.router.util.AppCache;
|
||||||
@ -27,7 +24,7 @@ import net.i2p.android.router.util.Util;
|
|||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.util.EepGet;
|
import net.i2p.util.EepGet;
|
||||||
|
|
||||||
class I2PWebViewClient extends WebViewClient {
|
public class I2PWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
private BGLoad _lastTask;
|
private BGLoad _lastTask;
|
||||||
/** save it here so we can dismiss it in onPageFinished() */
|
/** save it here so we can dismiss it in onPageFinished() */
|
||||||
@ -40,13 +37,9 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
private static final String ERROR_URL = "<p>Unable to load URL: ";
|
private static final String ERROR_URL = "<p>Unable to load URL: ";
|
||||||
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
|
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
|
||||||
|
|
||||||
public I2PWebViewClient(Context ctx) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
Util.e("Should override? " + url);
|
Util.d("Should override? " + url);
|
||||||
view.stopLoading();
|
view.stopLoading();
|
||||||
|
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
@ -61,7 +54,7 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
//reverse back to a i2p URI so we can load it here and not in ContentProvider
|
//reverse back to a i2p URI so we can load it here and not in ContentProvider
|
||||||
try {
|
try {
|
||||||
uri = CacheProvider.getI2PUri(uri);
|
uri = CacheProvider.getI2PUri(uri);
|
||||||
Util.e("Reversed content uri back to " + uri);
|
Util.d("Reversed content uri back to " + uri);
|
||||||
} catch (FileNotFoundException fnfe) {}
|
} catch (FileNotFoundException fnfe) {}
|
||||||
url = uri.toString();
|
url = uri.toString();
|
||||||
}
|
}
|
||||||
@ -75,7 +68,7 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
s = s.toLowerCase();
|
s = s.toLowerCase();
|
||||||
if (!(s.equals("http") || s.equals("https") ||
|
if (!(s.equals("http") || s.equals("https") ||
|
||||||
s.equals(CONTENT))) {
|
s.equals(CONTENT))) {
|
||||||
Util.e("Not loading URL " + url);
|
Util.d("Not loading URL " + url);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String h = uri.getHost();
|
String h = uri.getHost();
|
||||||
@ -128,14 +121,14 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
//view.loadUrl(url);
|
//view.loadUrl(url);
|
||||||
BGLoad task = new BackgroundLoad(view);
|
BGLoad task = new BackgroundLoad(view);
|
||||||
_lastTask = task;
|
_lastTask = task;
|
||||||
Util.e("Fetching via web or resource: " + url);
|
Util.d("Fetching via web or resource: " + url);
|
||||||
task.execute(url);
|
task.execute(url);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fail(View v, String s) {
|
private static void fail(View v, String s) {
|
||||||
Util.e("Fail toast: " + s);
|
Util.d("Fail toast: " + s);
|
||||||
Toast toast = Toast.makeText(v.getContext(), s, Toast.LENGTH_LONG);
|
Toast toast = Toast.makeText(v.getContext(), s, Toast.LENGTH_LONG);
|
||||||
toast.setGravity(Gravity.CENTER, 0, 0);
|
toast.setGravity(Gravity.CENTER, 0, 0);
|
||||||
toast.show();
|
toast.show();
|
||||||
@ -143,25 +136,25 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadResource(WebView view, String url) {
|
public void onLoadResource(WebView view, String url) {
|
||||||
Util.e("OLR URL: " + url);
|
Util.d("OLR URL: " + url);
|
||||||
super.onLoadResource(view, url);
|
super.onLoadResource(view, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||||
Util.e("ORE " + errorCode + " Desc: " + description + " URL: " + failingUrl);
|
Util.d("ORE " + errorCode + " Desc: " + description + " URL: " + failingUrl);
|
||||||
super.onReceivedError(view, errorCode, description, failingUrl);
|
super.onReceivedError(view, errorCode, description, failingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
Util.e("OPS URL: " + url);
|
Util.d("OPS URL: " + url);
|
||||||
super.onPageStarted(view, url, favicon);
|
super.onPageStarted(view, url, favicon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageFinished(WebView view, String url) {
|
public void onPageFinished(WebView view, String url) {
|
||||||
Util.e("OPF URL: " + url);
|
Util.d("OPF URL: " + url);
|
||||||
ProgressDialog d = _lastDialog;
|
ProgressDialog d = _lastDialog;
|
||||||
if (d != null && d.isShowing()) {
|
if (d != null && d.isShowing()) {
|
||||||
try {
|
try {
|
||||||
@ -173,6 +166,12 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
super.onPageFinished(view, url);
|
super.onPageFinished(view, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
|
||||||
|
Util.d("ORHAR URL: " + host);
|
||||||
|
super.onReceivedHttpAuthRequest(view, handler, host, realm);
|
||||||
|
}
|
||||||
|
|
||||||
/******
|
/******
|
||||||
API 11 :(
|
API 11 :(
|
||||||
|
|
||||||
@ -183,10 +182,10 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
|
|
||||||
******/
|
******/
|
||||||
|
|
||||||
void cancelAll() {
|
public void cancelAll() {
|
||||||
BGLoad task = _lastTask;
|
BGLoad task = _lastTask;
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
Util.e("Cancelling fetches");
|
Util.d("Cancelling fetches");
|
||||||
task.cancel(true);
|
task.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,12 +200,12 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
try {
|
try {
|
||||||
//reverse back to a i2p URI so we can delete it from the AppCache
|
//reverse back to a i2p URI so we can delete it from the AppCache
|
||||||
uri = CacheProvider.getI2PUri(uri);
|
uri = CacheProvider.getI2PUri(uri);
|
||||||
Util.e("clearing AppCache entry for current page " + uri);
|
Util.d("clearing AppCache entry for current page " + uri);
|
||||||
AppCache.getInstance(view.getContext()).removeCacheFile(uri);
|
AppCache.getInstance(view.getContext()).removeCacheFile(uri);
|
||||||
} catch (FileNotFoundException fnfe) {
|
} catch (FileNotFoundException fnfe) {
|
||||||
// this actually only deletes the row in the provider,
|
// this actually only deletes the row in the provider,
|
||||||
// not the actual file, but it will be overwritten in the reload.
|
// not the actual file, but it will be overwritten in the reload.
|
||||||
Util.e("clearing provider entry for current page " + url);
|
Util.d("clearing provider entry for current page " + url);
|
||||||
view.getContext().getContentResolver().delete(uri, null, null);
|
view.getContext().getContentResolver().delete(uri, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,20 +261,23 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
super(view, null);
|
super(view, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Integer doInBackground(String... urls) {
|
protected Integer doInBackground(final String... urls) {
|
||||||
publishProgress(Integer.valueOf(-1));
|
publishProgress(Integer.valueOf(-1));
|
||||||
try {
|
_view.post(new Runnable() {
|
||||||
_view.loadUrl(urls[0]);
|
@Override
|
||||||
} catch (Exception e) {
|
public void run() {
|
||||||
// CalledFromWrongThreadException
|
_view.loadUrl(urls[0]);
|
||||||
cancel(false);
|
}
|
||||||
}
|
});
|
||||||
return Integer.valueOf(0);
|
return Integer.valueOf(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void onProgressUpdate(Integer... progress) {
|
protected void onProgressUpdate(Integer... progress) {
|
||||||
if (isCancelled())
|
//if (isCancelled())
|
||||||
return;
|
// return;
|
||||||
|
|
||||||
|
|
||||||
//if (progress[0].intValue() < 0) {
|
//if (progress[0].intValue() < 0) {
|
||||||
// _dialog = ProgressDialog.show(_view.getContext(), "Loading", "some url");
|
// _dialog = ProgressDialog.show(_view.getContext(), "Loading", "some url");
|
||||||
// _dialog.setOnCancelListener(this);
|
// _dialog.setOnCancelListener(this);
|
||||||
@ -299,20 +301,20 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Integer doInBackground(String... urls) {
|
protected Integer doInBackground(String... urls) {
|
||||||
String url = urls[0];
|
final String url = urls[0];
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri);
|
File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri);
|
||||||
if (cacheFile.exists()) {
|
if (cacheFile.exists()) {
|
||||||
Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
|
final Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
|
||||||
Util.e("Loading " + url + " from resource cache " + resUri);
|
Util.d("Loading " + url + " from resource cache " + resUri);
|
||||||
_view.getSettings().setLoadsImagesAutomatically(true);
|
_view.post(new Runnable() {
|
||||||
_view.getSettings().setBlockNetworkLoads(false);
|
@Override
|
||||||
try {
|
public void run() {
|
||||||
_view.loadUrl(resUri.toString());
|
_view.getSettings().setLoadsImagesAutomatically(true);
|
||||||
} catch (Exception e) {
|
_view.getSettings().setBlockNetworkLoads(false);
|
||||||
// CalledFromWrongThreadException
|
_view.loadUrl(resUri.toString());
|
||||||
cancel(false);
|
}
|
||||||
}
|
});
|
||||||
// 1 means show the cache toast message
|
// 1 means show the cache toast message
|
||||||
return Integer.valueOf(1);
|
return Integer.valueOf(1);
|
||||||
}
|
}
|
||||||
@ -327,33 +329,33 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
fetcher.addStatusListener(this);
|
fetcher.addStatusListener(this);
|
||||||
boolean success = fetcher.fetch();
|
boolean success = fetcher.fetch();
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
Util.e("Fetch cancelled for " + url);
|
Util.d("Fetch cancelled for " + url);
|
||||||
return Integer.valueOf(0);
|
return Integer.valueOf(0);
|
||||||
}
|
}
|
||||||
try { out.close(); } catch (IOException ioe) {}
|
try { out.close(); } catch (IOException ioe) {}
|
||||||
if (success) {
|
if (success) {
|
||||||
// store in cache, get content URL, and load that way
|
// store in cache, get content URL, and load that way
|
||||||
// Set as current base
|
// Set as current base
|
||||||
Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
|
final Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
Util.e("Stored cache in " + content);
|
Util.d("Stored cache in " + content);
|
||||||
} else {
|
} else {
|
||||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
||||||
Util.e("cache create error");
|
Util.d("cache create error");
|
||||||
return Integer.valueOf(0);
|
return Integer.valueOf(0);
|
||||||
}
|
}
|
||||||
Util.e("loading data, base URL: " + uri + " content URL: " + content);
|
Util.d("loading data, base URL: " + uri + " content URL: " + content);
|
||||||
try {
|
_view.post(new Runnable() {
|
||||||
_view.loadUrl(content.toString());
|
@Override
|
||||||
} catch (Exception exc) {
|
public void run() {
|
||||||
// CalledFromWrongThreadException
|
_view.loadUrl(content.toString());
|
||||||
cancel(false);
|
}
|
||||||
}
|
});
|
||||||
Util.e("Fetch failed for " + url);
|
Util.d("Fetch failed for " + url);
|
||||||
} else {
|
} else {
|
||||||
// Load the error message in as a string, delete the file
|
// Load the error message in as a string, delete the file
|
||||||
String t = fetcher.getContentType();
|
final String t = fetcher.getContentType();
|
||||||
String e = fetcher.getEncoding();
|
final String e = fetcher.getEncoding();
|
||||||
String msg;
|
String msg;
|
||||||
int statusCode = fetcher.getStatusCode();
|
int statusCode = fetcher.getStatusCode();
|
||||||
if (statusCode < 0) {
|
if (statusCode < 0) {
|
||||||
@ -371,29 +373,31 @@ class I2PWebViewClient extends WebViewClient {
|
|||||||
DataHelper.read(fis, data);
|
DataHelper.read(fis, data);
|
||||||
msg = new String(data, e);
|
msg = new String(data, e);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Util.e("WVC", ioe);
|
Util.d("WVC", ioe);
|
||||||
msg = HEADER + "I/O error" + FOOTER;
|
msg = HEADER + "I/O error" + FOOTER;
|
||||||
} finally {
|
} finally {
|
||||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
||||||
try {
|
Util.d("loading error data URL: " + url);
|
||||||
Util.e("loading error data URL: " + url);
|
final String finalMsg = msg;
|
||||||
_view.loadDataWithBaseURL(url, msg, t, e, url);
|
_view.post(new Runnable() {
|
||||||
} catch (Exception exc) {
|
@Override
|
||||||
// CalledFromWrongThreadException
|
public void run() {
|
||||||
cancel(false);
|
_view.loadDataWithBaseURL(url, finalMsg, t, e, url);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Util.e("IOE for " + url, ioe);
|
Util.d("IOE for " + url, ioe);
|
||||||
} finally {
|
} finally {
|
||||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||||
}
|
}
|
||||||
return Integer.valueOf(0);
|
return Integer.valueOf(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void onProgressUpdate(Integer... progress) {
|
protected void onProgressUpdate(Integer... progress) {
|
||||||
if (isCancelled())
|
if (isCancelled())
|
||||||
return;
|
return;
|
@ -0,0 +1,31 @@
|
|||||||
|
package net.i2p.android.router.web;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PActivityBase;
|
||||||
|
import net.i2p.android.router.R;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class WebActivity extends I2PActivityBase {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
// Start with the base view
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
WebFragment f = new WebFragment();
|
||||||
|
if (getIntent().getData() != null) {
|
||||||
|
Bundle b = new Bundle();
|
||||||
|
b.putString(WebFragment.HTML_URI, getIntent().getDataString());
|
||||||
|
f.setArguments(b);
|
||||||
|
} else
|
||||||
|
f.setArguments(getIntent().getExtras());
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.main_fragment, f).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
WebFragment f = (WebFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||||
|
if (!f.onBackPressed())
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +1,68 @@
|
|||||||
package net.i2p.android.router.activity;
|
package net.i2p.android.router.web;
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.KeyEvent;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
import net.i2p.android.router.I2PFragmentBase;
|
||||||
import net.i2p.android.router.R;
|
import net.i2p.android.router.R;
|
||||||
|
|
||||||
public class WebActivity extends I2PActivityBase {
|
public class WebFragment extends I2PFragmentBase {
|
||||||
|
|
||||||
private I2PWebViewClient _wvClient;
|
private I2PWebViewClient _wvClient;
|
||||||
|
|
||||||
final static String HTML_RESOURCE_ID = "html_resource_id";
|
public final static String HTML_URI = "html_url";
|
||||||
|
public final static String HTML_RESOURCE_ID = "html_resource_id";
|
||||||
private static final String WARNING = "Warning - " +
|
private static final String WARNING = "Warning - " +
|
||||||
"any non-I2P links visited in this window are fetched over the regular internet and are " +
|
"any non-I2P links visited in this window are fetched over the regular internet and are " +
|
||||||
"not anonymous. I2P pages may not load images or CSS.";
|
"not anonymous. I2P pages may not load images or CSS.";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState)
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
{
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.web);
|
setHasOptionsMenu(true);
|
||||||
TextView tv = (TextView) findViewById(R.id.browser_status);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View v = inflater.inflate(R.layout.web, container, false);
|
||||||
|
TextView tv = (TextView) v.findViewById(R.id.browser_status);
|
||||||
tv.setText(WARNING);
|
tv.setText(WARNING);
|
||||||
WebView wv = (WebView) findViewById(R.id.browser_webview);
|
WebView wv = (WebView) v.findViewById(R.id.browser_webview);
|
||||||
_wvClient = new I2PWebViewClient(this);
|
_wvClient = new I2PWebViewClient();
|
||||||
wv.setWebViewClient(_wvClient);
|
wv.setWebViewClient(_wvClient);
|
||||||
wv.getSettings().setBuiltInZoomControls(true);
|
wv.getSettings().setBuiltInZoomControls(true);
|
||||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||||
wv.getSettings().setUseWideViewPort(true);
|
wv.getSettings().setUseWideViewPort(true);
|
||||||
Intent intent = getIntent();
|
String uriStr = getArguments().getString(HTML_URI);
|
||||||
Uri uri = intent.getData();
|
if (uriStr != null) {
|
||||||
if (uri != null) {
|
Uri uri = Uri.parse(uriStr);
|
||||||
//wv.getSettings().setLoadsImagesAutomatically(true);
|
//wv.getSettings().setLoadsImagesAutomatically(true);
|
||||||
//wv.loadUrl(uri.toString());
|
//wv.loadUrl(uri.toString());
|
||||||
// go thru the client so .i2p will work too
|
// go thru the client so .i2p will work too
|
||||||
_wvClient.shouldOverrideUrlLoading(wv, uri.toString());
|
_wvClient.shouldOverrideUrlLoading(wv, uri.toString());
|
||||||
} else {
|
} else {
|
||||||
wv.getSettings().setLoadsImagesAutomatically(false);
|
wv.getSettings().setLoadsImagesAutomatically(false);
|
||||||
int id = intent.getIntExtra(HTML_RESOURCE_ID, 0);
|
int id = getArguments().getInt(HTML_RESOURCE_ID, 0);
|
||||||
// no default, so restart should keep previous view
|
// no default, so restart should keep previous view
|
||||||
if (id != 0)
|
if (id != 0)
|
||||||
loadResource(wv, id);
|
loadResource(wv, id);
|
||||||
}
|
}
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadResource(WebView wv, int id) {
|
private void loadResource(WebView wv, int id) {
|
||||||
@ -62,7 +72,7 @@ public class WebActivity extends I2PActivityBase {
|
|||||||
try {
|
try {
|
||||||
in = getResources().openRawResource(id);
|
in = getResources().openRawResource(id);
|
||||||
|
|
||||||
int read = 0;
|
int read;
|
||||||
while ( (read = in.read(buf)) != -1)
|
while ( (read = in.read(buf)) != -1)
|
||||||
out.write(buf, 0, read);
|
out.write(buf, 0, read);
|
||||||
|
|
||||||
@ -79,25 +89,27 @@ public class WebActivity extends I2PActivityBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public boolean onBackPressed() {
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
WebView wv = (WebView) getActivity().findViewById(R.id.browser_webview);
|
||||||
WebView wv = (WebView) findViewById(R.id.browser_webview);
|
_wvClient.cancelAll();
|
||||||
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
|
wv.stopLoading();
|
||||||
_wvClient.cancelAll();
|
if (wv.canGoBack()) {
|
||||||
wv.stopLoading();
|
// TODO go into history, get url and call shouldOverrideUrlLoading()
|
||||||
if (wv.canGoBack()) {
|
// so we have control ??? But then back won't work right
|
||||||
// TODO go into history, get url and call shouldOverrideUrlLoading()
|
wv.goBack();
|
||||||
// so we have control ??? But then back won't work right
|
return true;
|
||||||
wv.goBack();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.fragment_web_actions, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
WebView wv = (WebView) findViewById(R.id.browser_webview);
|
WebView wv = (WebView) getActivity().findViewById(R.id.browser_webview);
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_reload:
|
case R.id.menu_reload:
|
||||||
_wvClient.cancelAll();
|
_wvClient.cancelAll();
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
* Copyright 2013 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a wizard model, including the pages/steps in the wizard, their dependencies, and their
|
||||||
|
* currently populated choices/values/selections.
|
||||||
|
*
|
||||||
|
* To create an actual wizard model, extend this class and implement {@link #onNewRootPageList()}.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractWizardModel implements ModelCallbacks {
|
||||||
|
protected Context mContext;
|
||||||
|
|
||||||
|
private List<ModelCallbacks> mListeners = new ArrayList<ModelCallbacks>();
|
||||||
|
private PageList mRootPageList;
|
||||||
|
|
||||||
|
public AbstractWizardModel(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
mRootPageList = onNewRootPageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this to define a new wizard model.
|
||||||
|
*/
|
||||||
|
protected abstract PageList onNewRootPageList();
|
||||||
|
|
||||||
|
public void onPageDataChanged(Page page) {
|
||||||
|
// can't use for each because of concurrent modification (review fragment
|
||||||
|
// can get added or removed and will register itself as a listener)
|
||||||
|
for (int i = 0; i < mListeners.size(); i++) {
|
||||||
|
mListeners.get(i).onPageDataChanged(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPageTreeChanged() {
|
||||||
|
// can't use for each because of concurrent modification (review fragment
|
||||||
|
// can get added or removed and will register itself as a listener)
|
||||||
|
for (int i = 0; i < mListeners.size(); i++) {
|
||||||
|
mListeners.get(i).onPageTreeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page findByKey(String key) {
|
||||||
|
return mRootPageList.findByKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(Bundle savedValues) {
|
||||||
|
for (String key : savedValues.keySet()) {
|
||||||
|
mRootPageList.findByKey(key).resetData(savedValues.getBundle(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerListener(ModelCallbacks listener) {
|
||||||
|
mListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bundle save() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
for (Page page : getCurrentPageSequence()) {
|
||||||
|
bundle.putBundle(page.getKey(), page.getData());
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current list of wizard steps, flattening nested (dependent) pages based on the
|
||||||
|
* user's choices.
|
||||||
|
*/
|
||||||
|
public List<Page> getCurrentPageSequence() {
|
||||||
|
ArrayList<Page> flattened = new ArrayList<Page>();
|
||||||
|
mRootPageList.flattenCurrentPageSequence(flattened);
|
||||||
|
return flattened;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterListener(ModelCallbacks listener) {
|
||||||
|
mListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
117
app/src/main/java/net/i2p/android/wizard/model/BranchPage.java
Normal file
117
app/src/main/java/net/i2p/android/wizard/model/BranchPage.java
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import net.i2p.android.wizard.ui.SingleChoiceFragment;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A page representing a branching point in the wizard. Depending on which choice is selected, the
|
||||||
|
* next set of steps in the wizard may change.
|
||||||
|
*/
|
||||||
|
public class BranchPage extends SingleFixedChoicePage {
|
||||||
|
private List<Branch> mBranches = new ArrayList<Branch>();
|
||||||
|
|
||||||
|
public BranchPage(ModelCallbacks callbacks, String title) {
|
||||||
|
super(callbacks, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page findByKey(String key) {
|
||||||
|
if (getKey().equals(key)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Branch branch : mBranches) {
|
||||||
|
Page found = branch.childPageList.findByKey(key);
|
||||||
|
if (found != null) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flattenCurrentPageSequence(ArrayList<Page> destination) {
|
||||||
|
super.flattenCurrentPageSequence(destination);
|
||||||
|
for (Branch branch : mBranches) {
|
||||||
|
if (branch.choice.equals(mData.getString(Page.SIMPLE_DATA_KEY))) {
|
||||||
|
branch.childPageList.flattenCurrentPageSequence(destination);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BranchPage addBranch(String choice, Page... childPages) {
|
||||||
|
PageList childPageList = new PageList(childPages);
|
||||||
|
for (Page page : childPageList) {
|
||||||
|
page.setParentKey(choice);
|
||||||
|
}
|
||||||
|
mBranches.add(new Branch(choice, childPageList));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment() {
|
||||||
|
return SingleChoiceFragment.create(getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOptionAt(int position) {
|
||||||
|
return mBranches.get(position).choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOptionCount() {
|
||||||
|
return mBranches.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getReviewItems(ArrayList<ReviewItem> dest) {
|
||||||
|
dest.add(new ReviewItem(getTitle(), mData.getString(SIMPLE_DATA_KEY), getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompleted() {
|
||||||
|
return !TextUtils.isEmpty(mData.getString(SIMPLE_DATA_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDataChanged() {
|
||||||
|
mCallbacks.onPageTreeChanged();
|
||||||
|
super.notifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BranchPage setValue(String value) {
|
||||||
|
mData.putString(SIMPLE_DATA_KEY, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Branch {
|
||||||
|
public String choice;
|
||||||
|
public PageList childPageList;
|
||||||
|
|
||||||
|
private Branch(String choice, PageList childPageList) {
|
||||||
|
this.choice = choice;
|
||||||
|
this.childPageList = childPageList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Conditional implements ModelCallbacks {
|
||||||
|
private Object mData = null;
|
||||||
|
private List<Page> mConditionalPages = new ArrayList<Page>();
|
||||||
|
|
||||||
|
public void onPageDataChanged(Page page) {
|
||||||
|
mData = page.getData().get(Page.SIMPLE_DATA_KEY);
|
||||||
|
for (Page p : mConditionalPages)
|
||||||
|
p.isSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPageTreeChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Condition {
|
||||||
|
public boolean isSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EqualCondition<T> implements Condition {
|
||||||
|
private T mCompValue;
|
||||||
|
|
||||||
|
public EqualCondition(Page page, T compValue) {
|
||||||
|
mCompValue = compValue;
|
||||||
|
mConditionalPages.add(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSatisfied() {
|
||||||
|
return mCompValue.equals(mData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NotEqualCondition<T> implements Condition {
|
||||||
|
private T mCompValue;
|
||||||
|
|
||||||
|
public NotEqualCondition(Page page, T compValue) {
|
||||||
|
mCompValue = compValue;
|
||||||
|
mConditionalPages.add(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSatisfied() {
|
||||||
|
return !(mCompValue.equals(mData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EqualAnyCondition<T> implements Condition {
|
||||||
|
private ArrayList<T> mChoices = new ArrayList<T>();
|
||||||
|
|
||||||
|
public EqualAnyCondition(Page page, T... choices) {
|
||||||
|
mChoices.addAll(Arrays.asList(choices));
|
||||||
|
mConditionalPages.add(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSatisfied() {
|
||||||
|
return mChoices.contains(mData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
import net.i2p.android.wizard.ui.I2PB64DestinationFragment;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A page asking for an I2P Destination.
|
||||||
|
* This must be the B64 representation of a Destination.
|
||||||
|
*/
|
||||||
|
public class I2PB64DestinationPage extends SingleTextFieldPage {
|
||||||
|
private String mFeedback;
|
||||||
|
|
||||||
|
public I2PB64DestinationPage(ModelCallbacks callbacks, String title) {
|
||||||
|
super(callbacks, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment() {
|
||||||
|
return I2PB64DestinationFragment.create(getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
String data = mData.getString(SIMPLE_DATA_KEY);
|
||||||
|
try {
|
||||||
|
new Destination().fromBase64(data);
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
mFeedback = "Invalid B64";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mFeedback = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean showFeedback() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFeedback() {
|
||||||
|
return mFeedback;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
import net.i2p.android.wizard.ui.I2PDestinationFragment;
|
||||||
|
import net.i2p.data.DataFormatException;
|
||||||
|
import net.i2p.data.Destination;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A page asking for an I2P Destination.
|
||||||
|
* This could be a B64, B32 or Addressbook domain.
|
||||||
|
*/
|
||||||
|
public class I2PDestinationPage extends SingleTextFieldPage {
|
||||||
|
private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
|
||||||
|
private String mFeedback;
|
||||||
|
|
||||||
|
public I2PDestinationPage(ModelCallbacks callbacks, String title) {
|
||||||
|
super(callbacks, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment() {
|
||||||
|
return I2PDestinationFragment.create(getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
String data = mData.getString(SIMPLE_DATA_KEY);
|
||||||
|
if (data.toLowerCase(Locale.US).endsWith(".b32.i2p")) { /* B32 */
|
||||||
|
if (data.length() != BASE32_HASH_LENGTH + 8) {
|
||||||
|
mFeedback = "Invalid B32";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (data.endsWith(".i2p")) { /* Domain */
|
||||||
|
// Valid
|
||||||
|
} else if (data.length() >= 516) { /* B64 */
|
||||||
|
try {
|
||||||
|
new Destination().fromBase64(data);
|
||||||
|
} catch (DataFormatException dfe) {
|
||||||
|
mFeedback = "Invalid B64";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mFeedback = "Not a valid I2P Destination";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mFeedback = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean showFeedback() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFeedback() {
|
||||||
|
return mFeedback;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface connecting {@link Page}, {@link AbstractWizardModel}, and model container
|
||||||
|
* objects (e.g. {@link net.i2p.android.i2ptunnel.TunnelWizardActivity}.
|
||||||
|
*/
|
||||||
|
public interface ModelCallbacks {
|
||||||
|
void onPageDataChanged(Page page);
|
||||||
|
void onPageTreeChanged();
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import net.i2p.android.wizard.ui.MultipleChoiceFragment;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A page offering the user a number of non-mutually exclusive choices.
|
||||||
|
*/
|
||||||
|
public class MultipleFixedChoicePage extends SingleFixedChoicePage {
|
||||||
|
public MultipleFixedChoicePage(ModelCallbacks callbacks, String title) {
|
||||||
|
super(callbacks, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment() {
|
||||||
|
return MultipleChoiceFragment.create(getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getReviewItems(ArrayList<ReviewItem> dest) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
ArrayList<String> selections = mData.getStringArrayList(Page.SIMPLE_DATA_KEY);
|
||||||
|
if (selections != null && selections.size() > 0) {
|
||||||
|
for (String selection : selections) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append(selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.add(new ReviewItem(getTitle(), sb.toString(), getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompleted() {
|
||||||
|
ArrayList<String> selections = mData.getStringArrayList(Page.SIMPLE_DATA_KEY);
|
||||||
|
return selections != null && selections.size() > 0;
|
||||||
|
}
|
||||||
|
}
|
171
app/src/main/java/net/i2p/android/wizard/model/Page.java
Normal file
171
app/src/main/java/net/i2p/android/wizard/model/Page.java
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
* Copyright 2013 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single page in the wizard.
|
||||||
|
*/
|
||||||
|
public abstract class Page implements PageTreeNode {
|
||||||
|
/**
|
||||||
|
* The key into {@link #getData()} used for wizards with simple (single) values.
|
||||||
|
*/
|
||||||
|
public static final String SIMPLE_DATA_KEY = "_";
|
||||||
|
|
||||||
|
protected ModelCallbacks mCallbacks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditionals that rely on this page.
|
||||||
|
*/
|
||||||
|
protected List<ModelCallbacks> mConditionals = new ArrayList<ModelCallbacks>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditions on whether this page should be used.
|
||||||
|
*/
|
||||||
|
protected List<Conditional.Condition> mConditions = new ArrayList<Conditional.Condition>();
|
||||||
|
/**
|
||||||
|
* Should all conditions be satisfied, or any of them?
|
||||||
|
*/
|
||||||
|
protected boolean mConditionAnd = false;
|
||||||
|
/**
|
||||||
|
* The last condition status.
|
||||||
|
*/
|
||||||
|
protected boolean mSatisfied = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current wizard values/selections.
|
||||||
|
*/
|
||||||
|
protected Bundle mData = new Bundle();
|
||||||
|
protected String mTitle;
|
||||||
|
protected boolean mRequired = false;
|
||||||
|
protected String mParentKey;
|
||||||
|
|
||||||
|
protected Page(ModelCallbacks callbacks, String title) {
|
||||||
|
mCallbacks = callbacks;
|
||||||
|
mTitle = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bundle getData() {
|
||||||
|
return mData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSatisfied() {
|
||||||
|
boolean ret = true;
|
||||||
|
if (mConditions.size() > 0) {
|
||||||
|
ret = false;
|
||||||
|
for (Conditional.Condition c : mConditions) {
|
||||||
|
if (c.isSatisfied()) {
|
||||||
|
ret = true;
|
||||||
|
if (!mConditionAnd) break;
|
||||||
|
} else if (mConditionAnd) {
|
||||||
|
ret = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the conditions have changed, update the page tree.
|
||||||
|
if (!(mSatisfied == ret)) {
|
||||||
|
mSatisfied = ret;
|
||||||
|
mCallbacks.onPageTreeChanged();
|
||||||
|
}
|
||||||
|
return mSatisfied;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRequired() {
|
||||||
|
return isSatisfied() && mRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setParentKey(String parentKey) {
|
||||||
|
mParentKey = parentKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page findByKey(String key) {
|
||||||
|
return getKey().equals(key) ? this : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flattenCurrentPageSequence(ArrayList<Page> dest) {
|
||||||
|
if (isSatisfied())
|
||||||
|
dest.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Fragment createFragment();
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return (mParentKey != null) ? mParentKey + ":" + mTitle : mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void getReviewItems(ArrayList<ReviewItem> dest);
|
||||||
|
|
||||||
|
public boolean isCompleted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetData(Bundle data) {
|
||||||
|
mData = data;
|
||||||
|
notifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyDataChanged() {
|
||||||
|
for (ModelCallbacks c : mConditionals) {
|
||||||
|
c.onPageDataChanged(this);
|
||||||
|
}
|
||||||
|
mCallbacks.onPageDataChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page setRequired(boolean required) {
|
||||||
|
mRequired = required;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page makeConditional(Conditional conditional) {
|
||||||
|
mConditionals.add(conditional);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Page setEqualCondition(Conditional conditional, T comp) {
|
||||||
|
Conditional.Condition c = conditional.new EqualCondition<T>(this, comp);
|
||||||
|
mConditions.add(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Page setNotEqualCondition(Conditional conditional, T comp) {
|
||||||
|
Conditional.Condition c = conditional.new NotEqualCondition<T>(this, comp);
|
||||||
|
mConditions.add(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Page setEqualAnyCondition(Conditional conditional, T... choices) {
|
||||||
|
Conditional.Condition c = conditional.new EqualAnyCondition<T>(this, choices);
|
||||||
|
mConditions.add(c);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page satisfyAllConditions(boolean conditionAnd) {
|
||||||
|
mConditionAnd = conditionAnd;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
48
app/src/main/java/net/i2p/android/wizard/model/PageList.java
Normal file
48
app/src/main/java/net/i2p/android/wizard/model/PageList.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
* Copyright 2013 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a list of wizard pages.
|
||||||
|
*/
|
||||||
|
public class PageList extends ArrayList<Page> implements PageTreeNode {
|
||||||
|
public PageList(Page... pages) {
|
||||||
|
for (Page page : pages) {
|
||||||
|
add(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page findByKey(String key) {
|
||||||
|
for (Page childPage : this) {
|
||||||
|
Page found = childPage.findByKey(key);
|
||||||
|
if (found != null) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flattenCurrentPageSequence(ArrayList<Page> dest) {
|
||||||
|
for (Page childPage : this) {
|
||||||
|
childPage.flattenCurrentPageSequence(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a node in the page tree. Can either be a single page, or a page container.
|
||||||
|
*/
|
||||||
|
public interface PageTreeNode {
|
||||||
|
public Page findByKey(String key);
|
||||||
|
public void flattenCurrentPageSequence(ArrayList<Page> dest);
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single line item on the final review page.
|
||||||
|
*
|
||||||
|
* @see net.i2p.android.wizard.ui.ReviewFragment
|
||||||
|
*/
|
||||||
|
public class ReviewItem {
|
||||||
|
public static final int DEFAULT_WEIGHT = 0;
|
||||||
|
|
||||||
|
private int mWeight;
|
||||||
|
private String mTitle;
|
||||||
|
private String mDisplayValue;
|
||||||
|
private String mPageKey;
|
||||||
|
|
||||||
|
public ReviewItem(String title, String displayValue, String pageKey) {
|
||||||
|
this(title, displayValue, pageKey, DEFAULT_WEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReviewItem(String title, String displayValue, String pageKey, int weight) {
|
||||||
|
mTitle = title;
|
||||||
|
mDisplayValue = displayValue;
|
||||||
|
mPageKey = pageKey;
|
||||||
|
mWeight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayValue() {
|
||||||
|
return mDisplayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayValue(String displayValue) {
|
||||||
|
mDisplayValue = displayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPageKey() {
|
||||||
|
return mPageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageKey(String pageKey) {
|
||||||
|
mPageKey = pageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return mTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
mTitle = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWeight() {
|
||||||
|
return mWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWeight(int weight) {
|
||||||
|
mWeight = weight;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 str4d
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.i2p.android.wizard.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import net.i2p.android.wizard.ui.SingleBooleanFragment;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
public class SingleFixedBooleanPage extends Page {
|
||||||
|
protected String mDesc = "";
|
||||||
|
protected String mLabel = null;
|
||||||
|
|
||||||
|
public SingleFixedBooleanPage(ModelCallbacks callbacks, String title) {
|
||||||
|
super(callbacks, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment() {
|
||||||
|
return SingleBooleanFragment.create(getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getReviewItems(ArrayList<ReviewItem> dest) {
|
||||||
|
dest.add(new ReviewItem(getTitle(),
|
||||||
|
mData.getBoolean(SIMPLE_DATA_KEY) ? "Yes" : "No", getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleFixedBooleanPage setLabel(String label) {
|
||||||
|
mLabel = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleFixedBooleanPage setDescription(String desc) {
|
||||||
|
mDesc = desc;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return mDesc;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user