Compare commits

...

360 Commits

Author SHA1 Message Date
e396b0b614 Client library 0.8
i2p.i2p tag: i2p-0.9.25
2016-04-17 13:08:33 +00:00
91cac6b743 Updated CHANGELOG 2016-04-17 12:53:16 +00:00
2a6015d890 Update README 2016-04-17 12:50:47 +00:00
049b094627 Updated translations 2016-04-17 12:41:59 +00:00
077d062e19 Update ignores 2016-04-17 12:39:33 +00:00
4ad483db71 Set colour of news notification to match persistent one 2016-04-17 12:29:47 +00:00
3edb8ad0c2 Fix client lib build system 2016-04-17 12:27:52 +00:00
769f41afe6 Update Gradle wrapper to 2.12 2016-03-28 10:30:49 +00:00
5a9d943a6c Upgrade support libs in client and helper libs, partially fix ProGuard issue
A release build can now be made (ProGuard no longer complains about duplicate
JARs), but the first build attempt after a clean fails because the I2P JARs
needed by the client library aren't on the javac classpath.
2016-03-28 10:27:16 +00:00
44fb246288 Upgrade support libraries to 23.2.1 2016-03-28 03:06:46 +00:00
3cc7498e66 Upgrade Androidplot, sort remote dependencies 2016-03-28 02:33:27 +00:00
84ce883285 Missing witness hash from previous commit 2016-03-28 02:17:43 +00:00
f8dd9df285 Migrate to support library PreferenceFragmentCompat 2016-03-28 02:15:32 +00:00
860cf6a658 Update Samsung 4.2 workaround for support lib 23.1.1 2016-03-09 20:39:15 +00:00
a24a50ce44 Missing files 2016-03-05 15:34:47 +00:00
fc9187297b Update CHANGELOG 2016-03-05 00:34:00 +00:00
eb26df874d NPE fix 2016-03-05 00:33:08 +00:00
27cbb1e57b Cut off speed at KBps 2016-03-05 00:10:16 +00:00
66aa79f90f Missing from previous commit 2016-03-04 23:44:31 +00:00
8b9a70b386 Fix localisation path 2016-03-04 23:35:17 +00:00
872a2d15e2 Update translations 2016-03-04 23:27:12 +00:00
7085567a08 Try to fix ClassNotFoundException 2016-03-04 23:15:58 +00:00
19b07a8a8c Add UI for copying server tunnel B32 2016-03-04 04:49:34 +00:00
0ed78a4806 Extract UI helper and AIDL interfaces to a helper library
For apps that only want to use e.g. HTTP proxy tunnel, this will decrease the
size of their APK by over 1.3 MB.
2015-11-23 03:44:22 +00:00
ca8fb4663f Missing from previous commit 2015-11-23 03:14:33 +00:00
ec34ce481e Moved client library into subdir 2015-11-23 03:04:43 +00:00
b0c4089e26 NPE fix 2015-11-22 04:52:13 +00:00
b23148c71d Updated translations 2015-11-22 04:37:39 +00:00
c9a336a0a5 Updated dependencies 2015-11-22 04:34:43 +00:00
0744426c15 Workaround for Samsung Android 4.2 bug 2015-11-22 03:39:54 +00:00
e6f4bd5531 Updated CHANGELOG 2015-10-10 22:16:07 +00:00
ab6f4799c9 0.9.22 2015-10-10 22:10:41 +00:00
f4beecead3 Added new client library translations 2015-10-10 11:43:12 +00:00
17e6d56bb7 Add false.i2p as SSL outproxy to default httpclient tunnel 2015-10-10 11:25:16 +00:00
29881b73f9 Fixed elevation of tabs 2015-10-10 11:16:50 +00:00
8c30582b9a Updated dependencies 2015-10-10 11:07:48 +00:00
06de8abb44 Re-add fixed translations for big text notification 2015-10-10 08:43:55 +00:00
a65ee65606 Updated translations 2015-10-09 22:56:07 +00:00
a6f49168b7 Add color to notification icon 2015-10-09 11:06:40 +00:00
1d1e6121fa Show addressbook tab when selecting address 2015-10-09 02:42:49 +00:00
30b86499cd Fix tab changing on rotate 2015-10-09 01:38:10 +00:00
bb8daa81d2 Translation bugfix 2015-10-09 00:12:13 +00:00
763ce08902 Updated CHANGELOG 2015-10-08 23:43:39 +00:00
a4e1055d86 Fixed bandwidth units in notification 2015-10-08 23:43:29 +00:00
ed89afd1bd Updated translations 2015-10-08 23:38:26 +00:00
13e26b4a1c Fix Activity transitions on Lollipop 2015-10-08 23:04:26 +00:00
3be56767a1 Move Lightning to recommended, Orweb to supported 2015-10-04 05:36:45 +00:00
d184019a8e Fix CalledFromWrongThreadException 2015-06-21 00:42:58 +00:00
6df542a162 Only linkify "irc://" in first start dialog if user can open them 2015-06-20 22:36:44 +00:00
96ca1d1a37 Updated CHANGELOG 2015-06-18 05:27:54 +00:00
6b585822f1 0.9.20
i2p.i2p revision: 747e9d409223a108623b0b38d084097335d6c195
2015-06-18 05:26:44 +00:00
c81c57daa0 Updated translations 2015-06-16 11:39:36 +00:00
f28be9cb02 Updated CHANGELOG 2015-06-16 11:35:28 +00:00
6582f67ed5 Fix tunnel backup quantity fetching in advanced tunnel prefs 2015-06-16 11:35:10 +00:00
d138c482d9 Change to bar plot, update range step when plot updates 2015-06-16 09:12:28 +00:00
fc2d962cad Hide stats in final shutdown 2015-06-16 07:32:48 +00:00
c541ae0347 Remove HTML entities from clock skew error, code style 2015-06-15 11:32:19 +00:00
d6e79ed0a7 Improved graph axes 2015-06-15 11:30:54 +00:00
7ec20fe60c Updated TODO 2015-06-15 11:30:13 +00:00
f3464c5095 SU3 news 2015-06-06 10:36:12 +00:00
32512fecbc Code style 2015-06-06 07:36:16 +00:00
1d7fcd47ef Filter proxy error page resources to remove routerconsole links 2015-06-06 03:56:44 +00:00
f156c591ad Disable countries table in netDB page (because no GeoIP data) 2015-06-06 00:07:18 +00:00
fb6ca0d61e Fixed NPE (linked to previous commit) 2015-06-05 23:50:33 +00:00
a4662984a7 Added missing setContentView() 2015-06-05 23:38:49 +00:00
6cccf1fb23 Don't change lights for graceful shutdown 2015-06-05 23:13:09 +00:00
39f32acd5b Update release notes 2015-06-04 10:45:21 +00:00
3d60d10f8e Make tunnel length default 2 in tunnel prefs, to match default i2ptunnel.config 2015-06-04 10:43:23 +00:00
8cbc11dff0 Updated translations 2015-06-03 00:03:14 +00:00
858f007e21 Client library 0.7
i2p.i2p tag: i2p-0.9.20
2015-06-02 23:53:02 +00:00
02f830e472 Upgraded support libraries 2015-06-02 23:40:26 +00:00
d32ef0bb9b Unregister onLocaleChanged broadcast listener 2015-05-29 11:02:21 +00:00
6ae103373e Helper method to check if tunnels are active 2015-05-27 10:41:33 +00:00
b2a82b9809 Use state to define isStarted() 2015-05-26 03:20:40 +00:00
73b6898d76 Bugfix: hard-code IRouterState action string so it always matches the manifest 2015-05-26 03:19:11 +00:00
be9336228c New helper bind method with callback to notify caller as soon as helper is bound 2015-05-26 03:18:43 +00:00
62546a779a Updated ignores 2015-05-24 07:20:33 +00:00
b4afc3b4b2 Initial unit tests for client library 2015-05-24 07:17:53 +00:00
4415a5fc25 Updated translations 2015-05-23 22:34:41 +00:00
bddb02f6ba Handle edge cases with ViewPager disabling after shutting down router 2015-05-21 12:14:34 +00:00
f2c3f30224 Move process killing on exit into I2PActivity 2015-05-21 02:57:51 +00:00
90ff16009c Improve usage stats unit handling 2015-05-21 02:25:56 +00:00
4cd97536c5 Center text in the three console status labels 2015-05-20 12:55:30 +00:00
4f2a5fdc1f Dynamically include proxy header files
These get translated with the proxy message classes included in i2ptunnel.jar,
but the footer appended by I2PTunnelHTTPClientBase.writeErrorMessage() is not
because its strings are included in i2ptunnel.war.
2015-05-20 12:51:08 +00:00
c35d13270a Scroll padding fix 2015-05-20 12:46:43 +00:00
6c9bb31da7 Dropped unused images 2015-05-20 06:03:15 +00:00
d5f6be4428 Better side shadow for two-pane tunnel details 2015-05-20 01:56:29 +00:00
77c6e8702d Side shadow for console status 2015-05-20 01:53:29 +00:00
c4fc10f552 Bugfix 2015-05-20 01:27:46 +00:00
4b0f8eb571 Performance tweak 2015-05-20 00:20:21 +00:00
f94d2c57db Move findViewById() calls into onCreateView() for efficiency 2015-05-20 00:18:20 +00:00
fc2d6f5f09 Fix landscape view when router not running 2015-05-19 23:59:37 +00:00
c6ab6f64e9 Reorganized console status view to only show net status when no internet 2015-05-19 23:54:07 +00:00
5315771118 Updated translations 2015-05-19 23:30:22 +00:00
39b8bff6a2 Updated translations 2015-05-19 11:42:46 +00:00
c2a7c8ebde String tweaks 2015-05-19 11:38:54 +00:00
32a8c71bf1 Show net status level 2015-05-19 11:32:30 +00:00
f35257fc65 Handle all net statuses, extract console strings 2015-05-19 11:22:07 +00:00
3ab1fa7c97 Extracted more strings for translation 2015-05-18 23:13:36 +00:00
551910abce Updated translations 2015-05-18 02:13:36 +00:00
170f8afafd Console layout fixes 2015-05-17 12:31:08 +00:00
20c08b1929 New console status layout 2015-05-17 12:22:21 +00:00
e943c31ef9 Improve layout performance 2015-05-17 06:44:25 +00:00
601b979d4e Re-increase routerlogo size, center portrait, fix view bounds issue 2015-05-17 05:15:19 +00:00
973808b392 Reduced size of routerlogo, moved usage stats to bottom toolbar 2015-05-17 05:01:02 +00:00
25811b742f Do full i2p.i2p rebuild after clean 2015-05-15 09:29:16 +00:00
2d57d7cad7 Updated translations 2015-05-15 08:13:03 +00:00
687a62656b Extracted remaining hard-coded strings that won't be changed 2015-05-15 08:05:53 +00:00
5a7c33d9b9 Updated translations 2015-05-15 05:44:31 +00:00
e66c98fbfe Language name tweak 2015-05-14 06:31:56 +00:00
2897bfcb77 Updated translations after string push 2015-05-13 11:34:18 +00:00
b8aca7badc Updated translations 2015-05-13 11:14:39 +00:00
edb2506083 Renamed addressbook page headers 2015-05-13 11:05:06 +00:00
eceb7f14a4 Change "No client tunnels..." to "No tunnels..." 2015-05-13 10:47:35 +00:00
b165f41266 Separate "graphs not ready" dialog, make dialogs non-cancelable 2015-05-13 10:45:01 +00:00
9dd15e550d Added icons to main settings categories 2015-05-12 10:41:53 +00:00
e94984901d Moved graphs from advanced to general 2015-05-12 10:17:06 +00:00
55d682fdfc Updated translations 2015-05-12 05:27:46 +00:00
c231a9f851 Fixed libjbigi building for x86 and mips 2015-05-11 12:13:12 +00:00
bbde471e99 Updated target SDK and build tools to API 22 2015-05-11 11:28:08 +00:00
51c5409f12 Updated Gradle plugin version 2015-05-11 11:24:10 +00:00
1ff96d5530 Updated translations after removing strings 2015-05-11 10:13:53 +00:00
5e099d0e3b Removed some old strings 2015-05-11 10:09:59 +00:00
c27e8ff513 Updated translations 2015-05-11 10:04:34 +00:00
632ed5b4b3 Extract more hard-coded strings 2015-05-11 09:44:35 +00:00
407b5c1441 Moved start/stop/restart all tunnels to TunnelsContainer 2015-05-10 13:41:14 +00:00
fd2050bc1f Updated translations 2015-05-09 13:35:59 +00:00
c615497a9e Translate news strings 2015-05-09 13:30:40 +00:00
e48b2f1dff Translate "shared clients" properly in-house 2015-05-09 13:21:31 +00:00
44cd0a6d55 Update router.config with language 2015-05-09 11:06:57 +00:00
196b6ffbbc Fixed plurals to match Transifex language rules 2015-05-08 13:17:30 +00:00
6cf01e3db0 Updated translations 2015-05-08 13:01:33 +00:00
204f2d8adc Lint 2015-05-08 13:01:14 +00:00
93853bd6b6 Lint: Unnecessary (un)boxing 2015-05-08 11:23:13 +00:00
4095b48a82 Lint 2015-05-08 11:20:49 +00:00
3ab1a68b94 Collated constants 2015-05-08 11:03:22 +00:00
7675d78d0d Converted hard-coded log strings to translatable strings 2015-05-08 10:59:19 +00:00
1b2fa9bda6 Rate graph styling 2015-05-08 10:36:01 +00:00
8819bbfa44 Removed redundant text 2015-05-08 08:58:46 +00:00
2e6ff0ac07 Fix default graphs 2015-05-08 08:18:22 +00:00
8d9a532424 Fixed console FAM visibility 2015-05-08 04:15:50 +00:00
6f5e3e2386 Replaced hard-coded activity titles with translatable strings 2015-05-08 04:07:30 +00:00
fb63f1eee7 Material design for TextResourceDialog 2015-05-07 02:08:20 +00:00
8c65812fa0 Add Ok button to FirstStartDialog 2015-05-07 01:37:55 +00:00
adfc6415b3 Display up button on Licenses 2015-05-07 01:37:39 +00:00
bb2a1c7c62 Prevent race condition if language choice restarts Activity 2015-05-06 22:15:38 +00:00
3d63269286 Choose language on first start 2015-05-06 13:04:53 +00:00
6dde8d2a88 Added Ok button to About dialog 2015-05-06 11:45:31 +00:00
d0c6bcff7d Tunnel interfaces for bidir and Streamr server tunnels 2015-05-06 06:50:26 +00:00
b938bc9698 NPE fix 2015-05-06 02:34:35 +00:00
2cb877d61b List available interfaces in tunnel preferences 2015-05-06 02:34:06 +00:00
77368e370b Updated translations 2015-05-06 02:04:50 +00:00
2e6589ea74 Status bar fixes 2015-05-06 01:56:28 +00:00
90367b0f9c Mark notification statuses for translation 2015-05-06 01:46:58 +00:00
97a350c482 Fixed deprecation 2015-05-06 01:26:53 +00:00
57d7708ae6 Enable language to be changed from default 2015-05-05 04:17:14 +00:00
913d39a9a2 Updated language code rewrites
See https://developer.android.com/reference/java/util/Locale.html for details
2015-05-05 03:15:50 +00:00
fa83742386 New client translations for ca 2015-05-05 03:14:42 +00:00
94539b8ebb Updated translations 2015-05-05 03:14:16 +00:00
bccbe30074 AppCompat v22.1.*: more deprecations 2015-05-02 11:54:21 +00:00
4e954eef50 AppCompat v22.1.*: Material design dialogs 2015-05-02 11:27:39 +00:00
6eeafb64f5 AppCompat v22.1.*: deprecations 2015-05-02 11:14:33 +00:00
d7184e9c99 Add up nav to one-pane tunnel details 2015-05-02 11:09:04 +00:00
be8016c02c Display selected tunnel setting in ListPreference summary 2015-05-02 04:59:32 +00:00
11d4e05518 Fix tunnel preference ordering 2015-05-02 00:02:19 +00:00
fdccadda04 Prevent PERSISTENT_KEY and NEW_KEYS from being set simultaneously 2015-05-01 23:42:46 +00:00
ccb1c73cf3 Lint 2015-05-01 12:29:39 +00:00
f3168f0dc2 Updated CHANGELOG 2015-05-01 12:08:40 +00:00
b4210cfb33 propagate from branch 'i2p.android.base.fragments' (head d590551a58afc8cebc67722afc73494e4f8836c0)
to branch 'i2p.android.base' (head e20ea0056b24eb0050ae3be6d84aac6e5157503d)
2015-05-01 10:25:16 +00:00
6e0a66292d Lint 2015-05-01 09:57:13 +00:00
71856397de Lint 2015-05-01 09:01:09 +00:00
568cdeca36 Lint: Rename values-id to values-in
See java.util.Locale for details
2015-05-01 08:58:15 +00:00
4e64fb3095 Updated translations 2015-05-01 08:48:16 +00:00
f33f26987b Fix adding entries to private addressbook 2015-05-01 08:39:13 +00:00
99fa7fbec6 Fix loading B64 from file 2015-05-01 08:37:35 +00:00
315931a032 Move from Maven Central to JCenter, fix up some dependencies
- JCenter is now the Android Studio default, it imports from MC
- Updated to materialish-progress 1.5
- Fixed viewpagerindicator hash (author accidentally overwrote it on MC)
2015-04-29 12:52:31 +00:00
8c63fb0ce2 Updated Android plugin
The IDE class path issue is fixed with Android Studio 1.2
2015-04-29 12:16:41 +00:00
31b7e18aad Updated Android support libraries
According to https://code.google.com/p/android/issues/detail?id=160591#c16 the
button theme bug is fixed. I still see it with API 10 (but fixed in 19+) in the
Android Studio renderer, but apparently it does work on Gingerbread devices.
2015-04-29 12:14:42 +00:00
b51ca8b27c Fix child fragments not getting an updated two-pane status on screen rotate 2015-04-28 12:51:22 +00:00
13af3d7869 Fix default router lights in landscape 2015-04-28 12:50:32 +00:00
c91de6f7ab Don't allow users to stop the router until it has finished starting 2015-04-25 05:35:08 +00:00
d961b5f8b1 Added scrolling to tunnel details 2015-04-21 13:05:07 +00:00
7d8141f62b Downgrade Android Support libraries to 21.0.3
Waiting on this issue to be resolved:
https://code.google.com/p/android/issues/detail?id=160591
2015-04-21 12:16:38 +00:00
6ef5d793f4 Save and restore console light status 2015-04-21 07:49:24 +00:00
c5c97366b8 Create the WizardModel before super.onCreate() to prevent NPEs in Fragments 2015-04-21 05:15:40 +00:00
124692db8e Reverse the Lollipop transition when finishing TunnelDetailActivity 2015-04-21 03:58:16 +00:00
72461cb678 Lollipop shared-element transition when opening TunnelDetailActivity 2015-04-21 03:52:29 +00:00
6f84f3ce06 Enable devices on API 21+ to use nice transitions 2015-04-21 03:08:05 +00:00
cbe21d94c9 Fix reloading of toolbar to update start/stop buttons in TDF 2015-04-21 02:47:19 +00:00
9c8c462089 Fixed up navigation issues from EditTunnelActivity and SettingsActivity 2015-04-20 22:56:09 +00:00
cc7c67c494 Updated TODO 2015-04-20 12:45:59 +00:00
0e14ed20e6 Split tunnel preferences into two Fragments
This prevents the advanced PreferenceScreen from going fullscreen.
2015-04-20 12:44:07 +00:00
d620535246 Fix ISE if screen rotating while router state changes 2015-04-20 12:40:19 +00:00
350d8a1363 Handle ActionBar up nav properly in SettingsActivity 2015-04-20 05:19:17 +00:00
23e80ec72b Split preferences into individual support PreferenceFragments 2015-04-20 04:11:05 +00:00
fd052f1b38 "Fix" race between RouterContext initialization and first console status update 2015-04-19 12:17:06 +00:00
865f5d271a Updated translations 2015-04-19 11:39:33 +00:00
d1293f5949 Mark property strings as non-translatable 2015-04-19 11:36:49 +00:00
2a354288bf Updated TODO 2015-04-19 11:26:14 +00:00
f20e82a25e Scroll entire console fragment in portrait (so stats are more visible on phones) 2015-04-19 11:24:23 +00:00
4a7899ce59 Update menus, FAMs etc. on router state change 2015-04-19 07:12:04 +00:00
2adc368307 Clear console status info as soon as the router has stopped 2015-04-19 07:02:12 +00:00
2bb57d2c4d Divider and margins for addressbook two-pane view 2015-04-19 05:15:07 +00:00
e649ac448b Added link icon to tunnel details page 2015-04-19 05:02:23 +00:00
337c93bb6c Updated TODO 2015-04-19 04:47:25 +00:00
f5624cf259 Differentiate between router shutting down and not running when tabs disabled 2015-04-19 04:46:28 +00:00
6afd9b950d Remove padding from top of addressbook list (headers do the job) 2015-04-19 04:45:56 +00:00
94c46d4a43 Divider for tunnel details, fixed strings 2015-04-19 04:33:05 +00:00
dcf072905c Move tunnel open link to details page 2015-04-19 04:19:52 +00:00
c67aeddb3b Material design: master-detail tunnel view
Detail shadowing method credit:
http://sapandiwakar.in/adding-shadows-to-views-in-android-using-9-patch-image/
2015-04-19 03:52:51 +00:00
f125079aa5 Added elevation to main toolbar
Pre-21 shadow uses code from the I/O 2014 source:
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/DrawShadowFrameLayout.java
2015-04-19 03:49:41 +00:00
0ddd9e5f4c Material design tablet margins 2015-04-19 02:22:39 +00:00
0074cf5a19 Use separate toolbar for tunnel details menu 2015-04-19 02:22:11 +00:00
19082db9a6 Start unifying list styles, first pass at improving tunnel details 2015-04-19 01:06:59 +00:00
109aeb7796 Disable tunnels and addressbook when router not running 2015-04-18 12:38:38 +00:00
3e06a11017 Updated TODO 2015-04-18 12:37:57 +00:00
dafd438982 Common style for pager indicators 2015-04-18 03:57:37 +00:00
063fd63fe2 Updated comment to match 2015-04-18 03:18:25 +00:00
5e27a93df9 Only show tunnels in two-pane for w720dp 2015-04-18 03:14:59 +00:00
c428195127 Attempted fix of IllegalStateException 2015-04-18 00:55:21 +00:00
1fba23a144 Use correct parent for TunnelDetailListener 2015-04-18 00:41:48 +00:00
3cc1e2b4bb Hide unusable console options when router is not running 2015-04-18 00:39:28 +00:00
77b05fa6e0 Open correct help page for correct tab 2015-04-18 00:32:55 +00:00
3553065ce2 Don't show start/stop/restart until TCG has started 2015-04-18 00:28:35 +00:00
167c06225f Added missing FAB to two-pane tunnels container 2015-04-18 00:10:00 +00:00
3420c04735 Improved title indicator for container ViewPagers 2015-04-17 23:42:50 +00:00
973282ab59 Updated TODO 2015-04-17 22:22:50 +00:00
7c04a96639 Re-enable tunnel editing 2015-04-17 22:22:36 +00:00
b2db1f4a6c Start TCG in LoadClientsJob 2015-04-17 11:24:08 +00:00
e3e0960ed8 Test settings navigation (to help pick up bugs like the IntListPreference one) 2015-04-16 11:24:08 +00:00
78ad153c47 Fixed integration tests for swapped tabs 2015-04-16 11:11:58 +00:00
fa6ba6bdce Updated CHANGELOG 2015-04-16 00:52:06 +00:00
8f302e6eeb 0.9.19.1
i2p.i2p tag: 0.9.19
2015-04-16 00:51:40 +00:00
93d2677b34 Fix for when an IntListPreference was previously stored in a ListPreference 2015-04-16 00:51:15 +00:00
1d02535158 StackOverflowException fix 2015-04-15 23:30:29 +00:00
955e40b0fc Updated client library dependencies 2015-04-13 12:06:01 +00:00
b4367843cd Gradle build script lint 2015-04-13 12:05:23 +00:00
76fd69544d Updated library versions 2015-04-13 12:03:18 +00:00
99b582a005 propagate from branch 'i2p.android.base' (head d8a24ace7d2d7616fcc2b12ac9a663f4cdbb7be8)
to branch 'i2p.android.base.fragments' (head 376be71890e893e97d2b8fed60d2a567c235ba99)
2015-04-13 08:29:00 +00:00
8e0e5ed5c4 Updated CHANGELOG 2015-04-13 02:21:38 +00:00
234bc6e5a0 0.9.19
i2p.i2p tag: i2p-0.9.19
2015-04-13 02:20:51 +00:00
b0131843ae Handle saved strings from older version 2015-04-13 01:52:24 +00:00
c32bda66b5 Client library 0.9.19
i2p.i2p tag: i2p-0.9.19
2015-04-13 01:18:21 +00:00
6d726df1dc Updated translations 2015-04-13 00:01:20 +00:00
5b5a99f512 Removed duplicate info from tunnel details page 2015-04-10 10:32:19 +00:00
a11dd1e4e6 Rebuilt libjbigi.so for armeabi with new build.sh, new build for armeabi-v7a
Both tested on my local device, router starts and builds tunnels.

armeabi:     native = 77% of pure java time
armeabi-v7a: native = 62% of pure java time
2015-04-10 04:03:36 +00:00
2190c59d73 Updated jbigi build.sh to support building all ABIs
Now uses make-standalone-toolchain.sh and flags obtained from
https://github.com/Rupan/gmp
2015-04-10 03:59:24 +00:00
c6e06e25a8 Updated translations 2015-04-09 23:50:36 +00:00
a559eb4fab Fixed location of uk translations 2015-04-09 23:49:02 +00:00
d1110aefc4 Fixes for jbigi building 2015-04-09 13:56:10 +00:00
9a5c63f620 Updated translations 2015-04-08 21:53:41 +00:00
d4e9195a6c Updated translations 2015-04-07 12:27:17 +00:00
e9d2b9f53c String fix 2015-04-07 12:26:01 +00:00
c124cafe3c Dividers and touch feedback for browser config list 2015-04-07 12:18:06 +00:00
24f0d72aae Touch feedback for tunnels and addresses in RecyclerView 2015-04-07 12:13:44 +00:00
8780d69be5 Decrease height of addressbook section headers 2015-04-07 12:11:59 +00:00
1853f5bcfd Add divider below addressbook labels in two pane mode 2015-04-07 12:10:32 +00:00
3f6caf18b4 Added missing headers to two-pane addressbook 2015-04-07 10:42:34 +00:00
97aa6e64bc Added dividers to tunnels and addressbook 2015-04-07 09:41:47 +00:00
f1bfd7d4aa Updated TODO 2015-04-06 23:31:49 +00:00
70ede5a370 Added alphanumeric section headers to addressbook 2015-04-06 23:31:14 +00:00
d1d06840fd Migrated tunnels list to RecyclerView 2015-04-06 03:25:05 +00:00
5f62418513 Moved state request to onResume() 2015-04-06 02:16:09 +00:00
5e6dab9bf1 Better handling of router state in AddressbookFragment 2015-04-06 00:49:28 +00:00
ef65a94e4c Migrated changes that merge didn't handle 2015-04-06 00:19:23 +00:00
c2e00ecf26 propagate from branch 'i2p.android.base' (head 83cce028d4603503d9530fcd828c4d9fa2a9ad7d)
to branch 'i2p.android.base.fragments' (head 049d93b11a6300da3b5b69212ba4f06d7720a080)
2015-04-06 00:11:03 +00:00
40f3d91ebb Tunnel logic bugfix 2015-04-05 13:12:14 +00:00
fae4b7e42d Bugfix in converting wizard data to TunnelConfig 2015-04-05 13:11:53 +00:00
eb700e34ba Migrated TunnelUtil.createConfigFromWizard() to use TunnelLogic 2015-04-05 12:32:56 +00:00
6828d985d2 Updated TODO 2015-04-05 05:00:06 +00:00
05a2132295 Updated translations 2015-04-05 04:58:43 +00:00
dbee390bba Updated changelog 2015-04-05 04:42:10 +00:00
0b5ae4edf8 Updated @since 2015-04-05 04:38:35 +00:00
bd741cd500 Use two buttons for graceful shutdown, restore notification title on cancel 2015-04-05 04:30:44 +00:00
4d7bc0f92b propagate from branch 'i2p.android.base' (head b76931f62977a8f15bd906d380795eddb0419d2a)
to branch 'i2p.android.base.zzz.graceful' (head 73baffc0c318d0c1b8c7ba05327ceefb2c84b341)
2015-04-05 02:41:52 +00:00
dac63d4401 NPE fix 2015-04-05 02:37:42 +00:00
8ed2ce3e3d Move AddressbookFragment to RecyclerView 2015-04-05 02:34:19 +00:00
c95f140fb4 NPE fix 2015-04-05 00:31:47 +00:00
4e21cc890f Better state handling for main UI fragments 2015-04-05 00:31:19 +00:00
cf3594962d NPE fix 2015-04-03 22:11:58 +00:00
9fa38c2840 Fixed addressbook search 2015-04-03 22:01:45 +00:00
2da8bbf214 Better descriptions for postman's tunnels 2015-04-03 12:03:55 +00:00
c984206f8e ActionBar up navigation 2015-04-03 12:03:42 +00:00
4a8b87d50c Reorder front UI tabs for better utility 2015-04-03 11:35:28 +00:00
a5a08a7282 Replace sub-toolbar with FAM for console menu 2015-04-03 11:33:08 +00:00
80f8469154 Some simple tests of the new tab UI 2015-04-03 00:12:41 +00:00
490137adc3 propagate from branch 'i2p.android.base' (head b76931f62977a8f15bd906d380795eddb0419d2a)
to branch 'i2p.android.base.fragments' (head 9af06a6f08e89e96c637b427e237538b600edc29)
2015-04-02 12:30:21 +00:00
d2959ddc3f Espresso testing support 2015-04-02 12:17:06 +00:00
6556156486 Fixed toolbar usage in activities 2015-04-01 12:40:15 +00:00
0095777a63 Replaced missing menu items 2015-04-01 12:24:58 +00:00
d8fc177b2d Better main tab locations for portrait and landscape 2015-04-01 11:36:22 +00:00
fcd5b2a503 Remove last of RouterContextProvider/RouterContextUser 2015-04-01 05:42:34 +00:00
0a5312e81b Missing from previous commit 2015-04-01 05:41:55 +00:00
7b67cf1581 Use Util.getRouterContext() for netDb list logic 2015-04-01 05:39:46 +00:00
2975b811d0 Use Util.getRouterContext() for tunnel list logic 2015-04-01 05:32:12 +00:00
ecb071ee88 Disable tunnel editing until ticket #815 is closed 2015-04-01 02:23:02 +00:00
624aa27e31 Updated README 2015-04-01 01:03:25 +00:00
a213ac51cd Finished diff after merge 2015-04-01 00:46:23 +00:00
db4a817ded propagate from branch 'i2p.android.base' (head 1b2db1a609968406ce50b5ab85bf3fe026d29d6f)
to branch 'i2p.android.base.fragments' (head d7c9088779c79670fb3be17fa039902345a5dc0e)
2015-04-01 00:40:08 +00:00
2407e9be46 Use LocalBroadcastManager to broadcast router state within the app 2015-03-31 23:51:18 +00:00
50973b5c06 Bugfix 2015-03-31 23:50:20 +00:00
8165e49300 Make handler static to prevent leaks 2015-03-31 22:43:59 +00:00
77749dd7f9 Lint removal 2015-03-31 13:57:21 +00:00
ef7e4cf610 Updated translations 2015-03-31 13:37:44 +00:00
a7b2bf148b Missing strings 2015-03-31 13:36:40 +00:00
3de78063f2 Missed changes 2015-03-31 12:36:38 +00:00
601d3c0ee3 Propagate visibility hints to TunnelListFragments too 2015-03-31 12:35:54 +00:00
3bcbd8c876 Store AddressbookFragment references, use to propagate visibility hints 2015-03-31 12:16:52 +00:00
e33be52b97 Only show "Reload subscriptions" for router addressbook 2015-03-31 12:15:20 +00:00
f513580525 Use Util.getRouterContext() in addressbook logic 2015-03-31 11:28:21 +00:00
ca2fde0b57 Changes missing from 1d97c209bb5a1634973da11e3dda3e96dad904e5 2015-03-31 11:27:51 +00:00
b442552146 NPE fix 2015-03-31 11:22:34 +00:00
9aee319607 Put tabs inside toolbar so action bar features can be used 2015-03-31 11:20:46 +00:00
d112e1a415 Listen for naming service changes 2015-03-31 03:20:03 +00:00
9cd6ab51ac Reorganize menus 2015-03-31 02:56:23 +00:00
6477f7d43f Common add button for tunnel views 2015-03-31 02:14:57 +00:00
65d1e6e089 NPE fix 2015-03-31 02:12:30 +00:00
6aff527456 Extracted FragmentPagerAdapter fragment recall into separate class 2015-03-30 22:42:31 +00:00
f32b896bb1 Fragment transaction bugfixes 2015-03-27 12:48:06 +00:00
58624ebf9d Add tabs using classes from Google IO sample code 2015-03-27 12:35:18 +00:00
db8355c477 Replaced navigation drawer and spinner with ViewPagers
First pass. Runs, but broken in many places.
2015-03-27 12:22:12 +00:00
a2278179f9 Missing file 2015-03-27 01:49:59 +00:00
3b3bcb30da Save tunnel config in onStop() if possible 2015-03-26 23:59:10 +00:00
d78b68d285 Tunnel preference bugfixes 2015-03-26 21:52:58 +00:00
00de9e98d2 Revert change accidentally checked into f4375e1bcf14aed1c7a94628599bead3bf866844 2015-03-26 13:49:57 +00:00
a543280a56 Updated translations 2015-03-26 13:23:27 +00:00
f036544744 Support numeric values for ListPreferences 2015-03-23 00:45:14 +00:00
8aa9ce9303 Bugfixes 2015-03-23 00:44:38 +00:00
1c605c16cf Edit tunnel UI 2015-03-20 03:58:23 +00:00
fc7f703658 Missing new widget from previous commit 2015-03-19 21:37:11 +00:00
163ef0512b Preferences XML for tunnel properties 2015-03-19 21:36:49 +00:00
6709bebc6f EditTextPreference that updates its summary (from Bote) 2015-03-12 23:01:11 +00:00
c7fad6940a IntEditTextPreference: default summary, use default value 2015-03-12 23:00:08 +00:00
4b1ee639b7 Tweaks after upstream changes 2015-03-12 00:07:35 +00:00
d2fa17fa66 Updates for upstream changes 2015-03-11 04:07:40 +00:00
87e12846b3 i2ptunnel.jar doesn't depend on I2PTunnelGUI anymore 2015-03-11 01:10:24 +00:00
97d1367180 Use TunnelConfig now in i2ptunnel.jar 2015-03-10 20:52:59 +00:00
a0419c9eb7 Add support-v4-preferencefragment as a dependency 2015-03-09 01:56:40 +00:00
5191118b87 Updated CHANGELOG 2015-03-05 22:24:39 +00:00
f5214e4b99 Include priority for logged strings (per upstream) 2015-03-04 19:07:57 +00:00
9564855cce Rewrite LogWriter to use LogWriterBase from upstream 2015-03-04 12:15:22 +00:00
17ab043a4b 0.9.18
i2p.i2p tag: i2p-0.9.18
2015-03-04 12:13:45 +00:00
32b2b0ce75 Updated TODO with Silent Store checklist (useful reference) 2015-03-04 12:12:44 +00:00
b77e2ebbe5 Updated translations 2015-03-04 01:11:48 +00:00
9eeab68cdb String translation fix 2015-03-04 01:11:44 +00:00
96257015a9 Language cleanups 2015-03-03 23:37:56 +00:00
d7f6e3688c UDP and NTCP ports, part 4 2015-03-03 22:50:26 +00:00
5ef434e29f Updated translations 2015-03-03 13:23:16 +00:00
852d695dac UDP and NTCP ports, part 3 2015-03-03 13:21:32 +00:00
96cb8ab410 Bugfix 2015-03-03 00:19:36 +00:00
cd158cca84 Updated translations 2015-03-02 22:57:37 +00:00
b71a0a27d3 Fixed help Activity back and up nav 2015-03-02 22:57:03 +00:00
64268c7af8 Activity title fixes 2015-03-02 21:45:13 +00:00
95749f032e I2PTunnel -> Tunnels / Hidden Services Manager; string tweaks 2015-03-02 21:29:34 +00:00
0ac1ae56b0 Dropped unnecessary part of tunnels guide 2015-03-02 21:17:43 +00:00
c52bc45910 Client library 0.5.1
i2p.i2p tag: i2p-0.9.18
2015-03-02 21:16:57 +00:00
064ebc6857 Moved missing class into client library 2015-03-02 11:03:02 +00:00
bae8c7ec00 propagate from branch 'i2p.android.base' (head 2cc736f12cfa9d56a7df3ab4be399cb256cbfc2c)
to branch 'i2p.android.base.zzz.graceful' (head deb95d5a40b64c460483f1e5af1a5624ff95fa6f)
2015-02-27 11:04:12 +00:00
80c8069769 propagate from branch 'i2p.android.base' (head 473e458dab49137f8211dcad60554cd90078807d)
to branch 'i2p.android.base.zzz.graceful' (head 57a30f761ea3d230301347dbb8b79bd3540f97a2)
2015-02-27 10:49:50 +00:00
zzz
5b4b151079 Preliminary support for graceful shutdown.
New buttons not yet implemented.
Untested.
2015-02-08 00:02:56 +00:00
399 changed files with 15599 additions and 6755 deletions

View File

@ -24,6 +24,7 @@ _jsp\.java$
/classes/
# Android-specific ignores
^lib/client/libs
^routerjars/libs
local.properties
signing.properties
@ -35,13 +36,13 @@ signing.properties
.*.iws
#Gradle
^.gradle
build
# I2P-specific ignores
^app/src/main/res/drawable/i2plogo.png
^app/src/main/res/raw/blocklist_txt
^app/src/main/res/raw/hosts_txt
^app/src/main/res/raw/.*_ht
^app/src/main/res/raw/license_
^app/src/main/res/raw/certificates_zip
^app/src/main/assets/themes/console/images

View File

@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, zh_CN: zh
lang_map = he: iw, id: in, pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, yi: ji, zh_CN: zh, zh_TW: zh-rTW
[I2P.android]
file_filter = app/src/main/res/values-<lang>/strings.xml
@ -9,9 +9,9 @@ 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
[I2P.android_lib_helper]
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
source_file = lib/helper/src/main/res/values/strings.xml
source_lang = en
type = ANDROID
minimum_perc = 50

View File

@ -1,11 +1,40 @@
0.9.18
0.9.25
* Fixed a bug on Samsung Android 4.2 devices
* Dependency improvements
* Translation updates
0.9.22 / 2015-10-10 / 0f73ef90b81e2cf3d55f0ea2b0a16e1f10da40ad
* Updated browser config guide
* Bug fixes and translation updates
0.9.20 / 2015-06-18 / 5fdaabeb5fa955caac90f1390adbdeaeae42fdf1
* Simplified the main interface
* Language can be configured
* Tunnels can now be edited
* Material design improvements
* Better support for tablets
* Improved graph rendering
* Bug fixes and translation updates
0.9.19.1 / 2015-04-15 / ed86e7e85161dbe3f15932fd4d195c551f8e2c71
* Fixed crash when opening advanced settings
0.9.19 / 2015-04-13 / 3cfb748946a5876dc06d5f81d811b142a88846f7
* Made internal state handling more stable
* Added graceful shutdown support
* Updated libjbigi to use GMP 6.0.0
* New libjbigi binary for armeabi-v7a to speed up newer devices
* Improved logging
* Bug fixes and translation updates
0.9.18 / 2015-03-04 / c2f4831a1617f4ce716a08640446fdd992c751ff
* I2P can start automatically when phone boots (configure in Setting)
* Updated browser configuration guides for Orfox and Firefox
* Tunnels for postman's mail server added to defaults (for new installs)
* Settings options for configuring UDP and TCP ports
* Bug fixes and translation updates
0.9.17.1 /2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
0.9.17.1 / 2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
* Fixed crashes in addressbook and netDb status page
* Fixed crash when opening an IRC client tunnel
* Updated translations

View File

@ -8,9 +8,9 @@
- Apache Ant 1.8.0 or higher
- I2P source
- Android SDK for API 21
- Android Build Tools 21.0.2
- Android Build Tools 21.1.2
- Android Support Repository
- Gradle 2.1
- Gradle 2.2.1
### Gradle
@ -48,6 +48,13 @@ systemProp.socksProxyPort=9150
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
3. Create a `local.properties` file in `i2p.android.base/lib/client` containing:
```
i2psrc=/path/to/i2p.i2p
ndk.dir=/path/to/ndk
```
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
```
@ -107,8 +114,8 @@ systemProp.socksProxyPort=9150
signing.keyId=
signing.password=
signing.secretKeyRingFile=/path/to/secring.gpg
ossrhUsername=
ossrhPassword=
NEXUS_USERNAME=
NEXUS_PASSWORD=
```
2. `gradle :client:uploadArchives`

73
TODO
View File

@ -1,31 +1,33 @@
# Fixes
- Better twopane column widths
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
- Create tunnel wizard
<zzz> in the tunnel create wizard:
<zzz> 'this could be the full base 64 destination key, or an i2p url from your address book"
<zzz> 'host name' better than 'URL'. Technically speaking, a host name is not a URL
<zzz> hmm would be nice if they could be shared-client or have an option
<zzz> was setting up email tunnels
- I2PTunnel details
<zzz> on the i2ptunnel details, it lists both a 'target' and 'access point' for clients, with the same info
<zzz> generally we use 'target' for servers and 'access point' for clients, never both
- Browser
<zzzccc> Bug report: i2p browser treats 302 as an error
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page
- Console text change
<zzz> "download" and "upload" at the bottom of the status is a little misleading..
<zzz> maybe 'downstream bandwidth' or 'inbound usage' ?
- Fix visibility of advanced tunnel parameter changes
<zzz> when I change an advanced tunnel param e.g. length or variance, the change isn't displayed, I have to go back and forward again to see the change
# New UI fixes
- Addressbook action items are in tunnel overflow menu after moving from console to tunnels
- Material design:
- Style for addressbook headers
- Change console FAM icon when possible
<zzz> on the bottom right, the + and x icons might be better as a double-up arrow and double-down arrow?
# Short-term
- Graceful shutdown option
<zzzccc> Request: graceful shutdown
- Disable uPnP when on cell networks
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
- I2PTunnel
- Improve tunnel list status indicators
- Show all messages somewhere
- Improve detail page, expose advanced settings
- Add edit page
- Icons/header images for tunnel types on details page
- Progress feedback for addressbook subscriptions reload
- Display release notes directly on new router version
- Fill out help pages
@ -46,11 +48,54 @@
- Also look at connection type: Connectivity.isConnectionFast()
- Expose log level overrides
- Improve graphs
- Show time on bottom axis
- Show fixed x range, not only available data
- Think about pan/zoom
- How to persist data across restarts?
# Silent Store approval checks to confirm/implement
- Known Vulnerabilities
- Apps will be tested to ensure that they are not susceptible to known
publicly disclosed vulnerabilities. For example:
- Heartbleed
- Poodle
- MasterKey
- Common Path Traversal attacks
- Common SQL Injection attacks
- Network Security Protocols
- All Apps that require transmission of data from the App to a system that
does not exist on the device must use, at a minimum, TLS1.1 standards.
However, Blackphone would prefer the usage of TLS1.2.
- Apps must not use algorithms for cryptographic purposes that are considered
obsolete or outdated i.e. MD5, SHA1, RC4, DES, or any encryption algorithm
that is weaker than AES128.
- Transport Layer Protection
- All network communication should be encrypted
- Not vulnerable to SSl Strip
- Data Leakage
- No storage of sensitive data outside of application sandbox
- Files should not be created with MODE_WORLD_READABLE or MODE_WORLD_WRITABLE
- Copy & Paste will be evaluated on a case by case basis
- App logs should not contain sensitive information
- Authentication and Authorization
- Validate that authentication credentials are not stored on the device
- Must use an approved password-based key derivation function ie. PBKDF2, scrypt
- Data-at-rest Encryption
- Must use at a minimum AES128 with modes CCM or GCM
- Should not store the encryption key on the file system
- Permission Checks
- The App must function with all permissions disabled
- Apps must not hard crash if a permission is disabled
- Apps should ask users to enable permissions that are disabled if needed to
function properly and explain why the permission is necessary
- Privacy Policy
- Apps must have a privacy policy that details how customer data is used,
stored, shared, etc...
- Apps must be configured with the customer opted out by default
- App logs should not contain PII
- Error Handling
- Apps should follow best-practices for error handling and logging
# Long-term
- Remote router support

View File

@ -2,13 +2,16 @@ apply plugin: 'com.android.application'
apply plugin: 'witness'
android {
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION as String
defaultConfig {
versionCode 4745225
versionName '0.9.17.1'
versionCode 4745231
versionName '0.9.22'
minSdkVersion 9
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
// For Espresso
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release
@ -16,8 +19,8 @@ android {
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix '.debug'
@ -31,6 +34,9 @@ android {
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'LICENSE.txt'
}
productFlavors {
free {
applicationId 'net.i2p.android'
@ -45,21 +51,46 @@ android {
}
dependencies {
// Local dependencies
compile project(':lib:client')
compile project(':routerjars')
compile project(':client')
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0'
compile files('libs/androidplot-core-0.6.1.jar')
// Android Support Repository dependencies
def supportVersion = '23.2.1'
compile "com.android.support:support-v4:$supportVersion"
compile "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:preference-v7:$supportVersion"
compile "com.android.support:preference-v14:$supportVersion"
compile "com.android.support:recyclerview-v7:$supportVersion"
// Remote dependencies
compile 'com.androidplot:androidplot-core:0.9.6'
compile 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
compile ('com.mcxiaoke.viewpagerindicator:library:2.4.1') {
exclude group: 'com.android.support', module: 'support-v4'
}
compile 'com.pnikosis:materialish-progress:1.7'
compile 'net.i2p.android.ext:floatingactionbutton:1.10.1'
compile 'org.sufficientlysecure:html-textview:1.3'
// Testing-only dependencies
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
}
dependencyVerification {
verify = [
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab',
'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448',
'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d',
'com.android.support:preference-v7:775101bd07bd052e455761c5c5d9523d7ad59f2f320e3e8cbde241fd6b1d6025',
'com.android.support:preference-v14:44881bb46094e86d0bc2426f205419674a5b4eb514b44b5a4659b5de29f71eb7',
'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe',
'com.androidplot:androidplot-core:1aaa931974da9d351976ed3d4b67170ac2a78be6c6afd13559ded5534eefc264',
'com.eowise:recyclerview-stickyheaders:7b236da49b33b840e9ba6e7e4182218d1a2d9047236fdbc3ca947352f9b0883b',
'com.mcxiaoke.viewpagerindicator:library:1e8aad664137f68abdfee94889f6da3dc98be652a235176a403965a07a25de62',
'com.pnikosis:materialish-progress:da089a90d1dab61e9b50038c09081019398f81190d12b0b567ce94b83ef8cf93',
'net.i2p.android.ext:floatingactionbutton:09d43e2d4ac04a91bf7a37e1ec48a8d220204e3a55dca72cd36cd9fa27461ade',
'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee',
]
}
@ -94,6 +125,32 @@ task copyI2PResources(type: Copy) {
into('raw') {
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
from(i2pbase + '/installer/resources/proxy') {
include { elem ->
elem.name.endsWith('.ht')
}
rename { String name ->
name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
}
filter { String line ->
// Remove links to routerconsole
def m = line =~ /127.0.0.1:7657/
if (m.getCount()) {
// Links around content
line = line.replaceAll(/<a href="http:\/\/127.0.0.1:7657[^>]*>(.+?)<\/a>/) { fullmatch, content ->
content
}
// Links in translation substitutions
line = line.replaceAll(/"<a href=\\"http:\/\/127.0.0.1:7657[^>]*>", "<\/a>"/, '"", ""')
}
// Remove "Configuration - Help - Addressbook" heading
def n = line =~ /Configuration.+Help.+Addressbook/
if (n.getCount())
""
else
line
}
}
from('../LICENSE.txt') { rename { 'license_app_txt' } }
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
from(i2pbase + '/licenses') {
@ -114,9 +171,7 @@ task copyI2PResources(type: Copy) {
]
}
rename { String name ->
String part = name.substring(8, name.lastIndexOf('.txt'))
String.format('license_%s_txt',
part.toLowerCase(Locale.US).replace('.', '_'))
name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
}
}
from certificatesZip
@ -148,6 +203,7 @@ task cleanI2PResources(type: Delete) {
delete fileTree('src/main/res/raw') {
include 'blocklist_txt'
include 'hosts_txt'
include '*_ht'
include 'license_*'
include 'certificates_zip'
}

Binary file not shown.

28
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,28 @@
# Add project specific ProGuard rules here.
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Workaround for Samsung Android 4.2 bug
# https://code.google.com/p/android/issues/detail?id=78377
# https://code.google.com/p/android/issues/detail?id=78377#c188
# https://code.google.com/p/android/issues/detail?id=78377#c302
-keepattributes **
-keep class !android.support.v7.view.menu.**,** {*;}
-dontpreverify
-dontoptimize
-dontshrink
-dontwarn **
-dontnote **

View File

@ -0,0 +1,108 @@
package net.i2p.android;
import android.test.ActivityInstrumentationTestCase2;
import net.i2p.android.router.R;
import static android.support.test.espresso.Espresso.closeSoftKeyboard;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.hasSibling;
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
public class I2PActivityTest extends ActivityInstrumentationTestCase2<I2PActivity> {
public I2PActivityTest() {
super(I2PActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
// For each test method invocation, the Activity will not actually be created
// until the first time this method is called.
getActivity();
}
public void testMainTabs() {
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
// Press "Tunnels" tab
onView(allOf(withText(R.string.label_tunnels),
not(isDescendantOfA(withId(R.id.main_scrollview))))).perform(click());
onView(withId(R.id.router_onoff_button)).check(matches(not(isDisplayed())));
onView(withText(R.string.label_i2ptunnel_client)).check(matches(isDisplayed()));
// Press "Addresses" tab
onView(withText(R.string.label_addresses)).perform(click());
onView(withText(R.string.label_i2ptunnel_client)).check(matches(not(isDisplayed())));
onView(withText(R.string.label_router)).check(matches(isDisplayed()));
// Press "Console" tab
onView(withText(R.string.label_console)).perform(click());
// Addressbook fragment should have been destroyed
onView(withText(R.string.label_router)).check(doesNotExist());
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
}
public void testMainSwipe() {
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
onView(withId(R.id.router_onoff_button)).check(matches(not(isDisplayed())));
onView(withText(R.string.label_i2ptunnel_client)).check(matches(isDisplayed()));
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
// TODO: test tunnels ViewPager
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
onView(withText(R.string.label_i2ptunnel_client)).check(matches(not(isDisplayed())));
onView(withText(R.string.label_router)).check(matches(isDisplayed()));
// TODO: test addressbook ViewPager
}
public void testSettingsNavigation() {
// Open settings menu
openActionBarOverflowOrOptionsMenu(getActivity());
onView(withText(R.string.menu_settings)).perform(click());
// Open bandwidth page
onView(withText(R.string.settings_label_bandwidth_net)).perform(click());
onView(withText(R.string.settings_label_startOnBoot)).check(matches(isDisplayed()));
pressBack();
// Open graphs page
onView(withText(R.string.label_graphs)).perform(click());
onView(withText(R.string.router_not_running)).check(matches(isDisplayed()));
pressBack();
// Open logging page
onView(withText(R.string.settings_label_logging)).perform(click());
onView(withText(R.string.settings_label_default_log_level)).check(matches(isDisplayed()));
pressBack();
// Open addressbook page
onView(withText(R.string.label_addressbook)).perform(click());
onView(withText("Subscriptions")).check(matches(isDisplayed()));
closeSoftKeyboard();
pressBack();
// Open graphs page
onView(withText(R.string.settings_label_advanced)).perform(click());
onView(withText(R.string.settings_label_transports)).check(matches(isDisplayed()));
pressBack();
// Check back exits settings
onView(withText(R.string.settings_label_advanced)).check(matches(isDisplayed()));
pressBack();
onView(withText(R.string.settings_label_advanced)).check(doesNotExist());
}
}

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
android:title="@string/settings_label_bandwidth_net">
<extra
android:name="settings"
android:value="net" />
</header>
<header
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
android:title="@string/label_graphs">
<extra
android:name="settings"
android:value="graphs" />
</header>
<header
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
android:title="@string/settings_label_logging">
<extra
android:name="settings"
android:value="logging" />
</header>
<header
android:title="@string/label_addressbook">
<intent
android:targetPackage="net.i2p.android.debug"
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
</header>
<header
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
android:title="@string/settings_label_advanced">
<extra
android:name="settings"
android:value="advanced" />
</header>
</preference-headers>

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<Preference android:title="@string/settings_label_bandwidth_net">
<intent
android:targetPackage="net.i2p.android.debug"
android:targetClass="net.i2p.android.router.SettingsActivity"
android:action="net.i2p.android.router.PREFS_NET" />
</Preference>
<Preference android:title="@string/label_graphs">
<intent
android:targetPackage="net.i2p.android.debug"
android:targetClass="net.i2p.android.router.SettingsActivity"
android:action="net.i2p.android.router.PREFS_GRAPHS" />
</Preference>
<Preference android:title="@string/settings_label_logging">
<intent
android:targetPackage="net.i2p.android.debug"
android:targetClass="net.i2p.android.router.SettingsActivity"
android:action="net.i2p.android.router.PREFS_LOGGING" />
</Preference>
<Preference android:title="@string/label_addressbook">
<intent
android:targetPackage="net.i2p.android.debug"
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
</Preference>
<Preference android:title="@string/settings_label_advanced">
<intent
android:targetPackage="net.i2p.android.debug"
android:targetClass="net.i2p.android.router.SettingsActivity"
android:action="net.i2p.android.router.PREFS_ADVANCED" />
</Preference>
</PreferenceScreen>

View File

@ -3,6 +3,9 @@
package="net.i2p.android.router"
android:installLocation="auto">
<uses-sdk xmlns:tools="http://schemas.android.com/tools"
tools:overrideLibrary="android.support.v14.preference" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@ -29,10 +32,11 @@
</receiver>
<activity
android:name=".MainActivity"
android:name="net.i2p.android.I2PActivity"
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name"
android:launchMode="singleTop">
<!-- Console filters -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -41,35 +45,48 @@
<action android:name="net.i2p.android.router.START_I2P" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- Addressbook filters -->
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
</activity>
<activity
android:name=".NewsActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P News"
android:parentActivityName=".MainActivity">
android:label="@string/label_news"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.help.HelpActivity"
android:label="Help"
android:parentActivityName=".MainActivity">
android:label="@string/menu_help"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.help.BrowserConfigActivity"
android:label="Browser Configuration"
android:parentActivityName=".MainActivity">
android:label="@string/label_browser_configuration"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".LicenseActivity"
android:label="I2P License Information"
android:label="@string/label_licenses"
android:parentActivityName="net.i2p.android.help.HelpActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -94,81 +111,61 @@
</activity>
<activity
android:name=".SettingsActivity"
android:label="I2P Settings"
android:parentActivityName=".MainActivity">
android:label="@string/menu_settings"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookSettingsActivity"
android:label="Addressbook Settings"
android:label="@string/label_addressbook"
android:launchMode="singleTop"
android:parentActivityName=".addressbook.AddressbookActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<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" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookAddWizardActivity"
android:label="Add new Destination"
android:parentActivityName=".addressbook.AddressbookActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<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" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
android:label="I2PTunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.preferences.EditTunnelActivity"
android:label="@string/edit_tunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelDetailActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.i2ptunnel.TunnelDetailActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
android:label="Tunnel Creation Wizard"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".log.LogActivity"
android:label="I2P Logs"
android:parentActivityName=".MainActivity">
android:label="@string/label_logs"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".log.LogDetailActivity"
android:label="Log Entry"
android:label="@string/log_entry"
android:parentActivityName=".log.LogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -176,29 +173,29 @@
</activity>
<activity
android:name=".stats.RateGraphActivity"
android:label="Rate Graph"
android:parentActivityName=".MainActivity">
android:label="@string/label_graphs"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".stats.PeersActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P Peers and Transport Status"
android:label="@string/label_peers_status"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".netdb.NetDbActivity"
android:label="NetDB"
android:parentActivityName=".MainActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".netdb.NetDbDetailActivity"

View File

@ -0,0 +1,140 @@
package android.support.v4.view;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
public class CustomViewPager extends ViewPager {
private boolean mEnabled;
private int mFixedPage;
private int mFixedPageString;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mEnabled = false;
mFixedPage = -1;
mFixedPageString = 0;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mEnabled && mFixedPage < 0 && super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mEnabled && mFixedPage < 0 && super.onInterceptTouchEvent(event);
}
@Override
public void setCurrentItem(int item) {
if ((mEnabled && (mFixedPage < 0 || item == mFixedPage))
|| (!mEnabled && item == 0))
super.setCurrentItem(item);
else if (!mEnabled)
Toast.makeText(getContext(), Util.getRouterContext() == null ?
R.string.router_not_running : R.string.router_shutting_down,
Toast.LENGTH_SHORT).show();
else if (mFixedPageString > 0)
Toast.makeText(getContext(), getContext().getString(mFixedPageString),
Toast.LENGTH_SHORT).show();
}
public void setPagingEnabled(boolean enabled) {
mEnabled = enabled;
updatePagingState();
}
public void setFixedPage(int page, int res) {
mFixedPage = page;
mFixedPageString = res;
updatePagingState();
}
public void updatePagingState() {
if (mEnabled) {
if (mFixedPage >= 0 && getCurrentItem() != mFixedPage)
setCurrentItem(mFixedPage);
} else if (getCurrentItem() != 0)
setCurrentItem(0);
}
public static class SavedState extends ViewPager.SavedState {
boolean enabled;
int fixedPage;
int fixedPageString;
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(enabled ? 1 : 0);
out.writeInt(fixedPage);
out.writeInt(fixedPageString);
}
@Override
public String toString() {
return "CustomViewPager.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " enabled=" + enabled + " fixedPage=" + fixedPage + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
SavedState(Parcel in, ClassLoader loader) {
super(in, loader);
enabled = in.readInt() != 0;
fixedPage = in.readInt();
fixedPageString = in.readInt();
}
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.enabled = mEnabled;
ss.fixedPage = mFixedPage;
ss.fixedPageString = mFixedPageString;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
mEnabled = ss.enabled;
mFixedPage = ss.fixedPage;
mFixedPageString = ss.fixedPageString;
}
}

View File

@ -1,162 +0,0 @@
/*
* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Slight modifications and bugfixes by Sponge <sponge@mail.i2p>
* These modifications are released under the WTFPL (any version)
*
* We don't need negative numbers yet, and may never need to.
*
* XML Usage example:
*
* <com.hlidskialf.android.preference.SeekBarPreference android:key="duration"
* android:title="Duration of something"
* android:summary="How long something will last"
* android:dialogMessage="Something duration"
* android:defaultValue="5"
* android:text=" minutes"
* android:max="60"
* />
*
*/
package com.hlidskialf.android.preference;
import android.content.Context;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
private static final String androidns = "http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText;
private TextView mValueText;
private Context mContext;
private String mDialogMessage, mSuffix;
private String mDefault = "0";
private int mMax = 0;
private int mValue = 0;
private int mDirection = LinearLayout.HORIZONTAL;
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
int dialogMessageR = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
mDialogMessage = (dialogMessageR == 0)
? attrs.getAttributeValue(androidns, "dialogMessage")
: context.getResources().getString(dialogMessageR);
int textR = attrs.getAttributeResourceValue(androidns, "text", 0);
mSuffix = (textR == 0)
? attrs.getAttributeValue(androidns, "text")
: context.getResources().getString(textR);
mDefault = attrs.getAttributeValue(androidns, "defaultValue");
mMax = Integer.parseInt(attrs.getAttributeValue(androidns, "max"));
if (attrs.getAttributeValue(androidns, "direction") != null) {
mDirection = Integer.parseInt(attrs.getAttributeValue(androidns, "direction"));
}
}
@Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 10);
// Set the width so that it is as usable as possible.
// We multiplymMax so that the smaller ranges will get a bigger area.
if (mDirection == LinearLayout.HORIZONTAL) {
layout.setMinimumWidth(mMax*5);
} else {
layout.setMinimumHeight(mMax*5);
}
mSplashText = new TextView(mContext);
if (mDialogMessage != null) {
mSplashText.setText(mDialogMessage);
}
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
// Move the bar away from the changing text, so you can see it, and
// move it away from the edges to improve usability for the end-ranges.
mSeekBar.setPadding(6, 30, 6, 6);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist()) {
mValue = Integer.parseInt(getPersistedString(mDefault));
}
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
return layout;
}
@Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
}
@Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
super.onSetInitialValue(restore, defaultValue);
if (restore) {
mValue = shouldPersist() ? Integer.parseInt(getPersistedString(mDefault)) : 0;
} else {
mValue = (Integer) defaultValue;
}
}
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
String t = String.valueOf(value);
mValueText.setText(mSuffix == null ? t : t.concat(mSuffix));
if (shouldPersist()) {
persistString(t);
}
callChangeListener(Integer.valueOf(value));
}
public void onStartTrackingTouch(SeekBar seek) {
}
public void onStopTrackingTouch(SeekBar seek) {
}
public void setMax(int max) {
mMax = max;
}
public int getMax() {
return mMax;
}
public void setProgress(int progress) {
mValue = progress;
if (mSeekBar != null) {
mSeekBar.setProgress(progress);
}
}
public int getProgress() {
return mValue;
}
}

View File

@ -0,0 +1,161 @@
package com.pavelsikun.seekbarpreference;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.preference.Preference;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import net.i2p.android.router.R;
/**
* Based on MaterialSeekBarController created by mrbimc on 30.09.15.
*/
public class MaterialSeekBarController implements SeekBar.OnSeekBarChangeListener {
private final String TAG = getClass().getName();
public static final int DEFAULT_CURRENT_VALUE = 50;
private static final int DEFAULT_MAX_VALUE = 100;
private static final String DEFAULT_MEASUREMENT_UNIT = "";
private int mMaxValue;
private int mMaxDigits;
private int mCurrentValue;
private String mMeasurementUnit;
private SeekBar mSeekBar;
private TextView mSeekBarValue;
private TextView mMeasurementUnitView;
private Context mContext;
private Persistable mPersistable;
public MaterialSeekBarController(Context context, AttributeSet attrs, Persistable persistable) {
mContext = context;
mPersistable = persistable;
init(attrs, null);
}
private void init(AttributeSet attrs, View view) {
setValuesFromXml(attrs);
if(view != null) onBindView(view);
}
private void setValuesFromXml(@Nullable AttributeSet attrs) {
if (attrs == null) {
mCurrentValue = DEFAULT_CURRENT_VALUE;
mMaxValue = DEFAULT_MAX_VALUE;
mMeasurementUnit = DEFAULT_MEASUREMENT_UNIT;
} else {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SeekBarPreference);
try {
mMaxValue = a.getInt(R.styleable.SeekBarPreference_msbp_maxValue, DEFAULT_MAX_VALUE);
mCurrentValue = a.getInt(R.styleable.SeekBarPreference_msbp_defaultValue, DEFAULT_CURRENT_VALUE);
if(mCurrentValue > mMaxValue) mCurrentValue = mMaxValue / 2;
mMeasurementUnit = a.getString(R.styleable.SeekBarPreference_msbp_measurementUnit);
if (mMeasurementUnit == null)
mMeasurementUnit = DEFAULT_MEASUREMENT_UNIT;
} finally {
a.recycle();
}
}
mMaxDigits = (int) Math.log10(mMaxValue) + 1;
}
public void onBindView(@NonNull View view) {
mSeekBar = (SeekBar) view.findViewById(R.id.seekbar);
mSeekBar.setMax(mMaxValue);
mSeekBar.setOnSeekBarChangeListener(this);
mSeekBarValue = (TextView) view.findViewById(R.id.seekbar_value);
setPaddedValue(mCurrentValue);
mMeasurementUnitView = (TextView) view.findViewById(R.id.measurement_unit);
mMeasurementUnitView.setText(mMeasurementUnit);
mSeekBar.setProgress(mCurrentValue);
if (!view.isEnabled()) {
mSeekBar.setEnabled(false);
mSeekBarValue.setEnabled(false);
}
}
protected void onSetInitialValue(boolean restoreValue, @NonNull Object defaultValue) {
mCurrentValue = mMaxValue / 2;
try {
mCurrentValue = (Integer) defaultValue;
} catch (Exception ex) {
Log.e(TAG, "Invalid default value: " + defaultValue.toString());
}
}
public void setEnabled(boolean enabled) {
if (mSeekBar != null) mSeekBar.setEnabled(enabled);
if (mSeekBarValue != null) mSeekBarValue.setEnabled(enabled);
}
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
if (mSeekBar != null) mSeekBar.setEnabled(!disableDependent);
if (mSeekBarValue != null) mSeekBarValue.setEnabled(!disableDependent);
}
//SeekBarListener:
@Override
public void onProgressChanged(@NonNull SeekBar seekBar, int progress, boolean fromUser) {
mCurrentValue = progress;
setPaddedValue(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(@NonNull SeekBar seekBar) {
setCurrentValue(mCurrentValue);
}
private void setPaddedValue(int value) {
mSeekBarValue.setText(String.format("%0" + mMaxDigits +"d", value));
}
//public methods for manipulating this widget from java:
public void setCurrentValue(int value) {
mCurrentValue = value;
if (mPersistable != null) mPersistable.onPersist(value);
}
public int getCurrentValue() {
return mCurrentValue;
}
public void setMaxValue(int maxValue) {
mMaxValue = maxValue;
if (mSeekBar != null) mSeekBar.setMax(mMaxValue);
}
public int getMaxValue() {
return mMaxValue;
}
public void setMeasurementUnit(String measurementUnit) {
mMeasurementUnit = measurementUnit;
if (mMeasurementUnitView != null) mMeasurementUnitView.setText(mMeasurementUnit);
}
public String getMeasurementUnit() {
return mMeasurementUnit;
}
}

View File

@ -0,0 +1,8 @@
package com.pavelsikun.seekbarpreference;
/**
* Created by mrbimc on 04.10.15.
*/
public interface Persistable {
void onPersist(int value);
}

View File

@ -0,0 +1,104 @@
package com.pavelsikun.seekbarpreference;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import net.i2p.android.router.R;
public class SeekBarPreference extends Preference implements Persistable {
private MaterialSeekBarController mController;
public SeekBarPreference(Context context) {
super(context);
init(null);
}
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(AttributeSet attrs) {
setLayoutResource(R.layout.seekbar_preference);
mController = new MaterialSeekBarController(getContext(), attrs, this);
}
@Override
public void onBindViewHolder(@NonNull PreferenceViewHolder viewHolder) {
super.onBindViewHolder(viewHolder);
mController.onBindView(viewHolder.itemView);
}
@Override
protected Object onGetDefaultValue(@NonNull TypedArray ta, int index) {
if(mController != null) return ta.getInt(index, mController.getCurrentValue());
else return null;
}
@Override
protected void onSetInitialValue(boolean restoreValue, @NonNull Object defaultValue) {
int average = mController.getMaxValue() / 2;
if(restoreValue) mController.setCurrentValue(getPersistedInt(average));
else mController.onSetInitialValue(restoreValue, defaultValue);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mController.setEnabled(enabled);
}
@Override
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
super.onDependencyChanged(dependency, disableDependent);
mController.onDependencyChanged(dependency, disableDependent);
}
@Override
public void onPersist(int value) {
persistInt(value);
notifyChanged();
}
public String getMeasurementUnit() {
return mController.getMeasurementUnit();
}
public void setMeasurementUnit(String measurementUnit) {
mController.setMeasurementUnit(measurementUnit);
}
public int getMaxValue() {
return mController.getMaxValue();
}
public void setMaxValue(int maxValue) {
mController.setMaxValue(maxValue);
}
public int getCurrentValue() {
return mController.getCurrentValue();
}
public void setCurrentValue(int value) {
mController.setCurrentValue(value);
}
}

View File

@ -0,0 +1,364 @@
package net.i2p.android;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.i2ptunnel.TunnelsContainer;
import net.i2p.android.router.ConsoleContainer;
import net.i2p.android.router.MainFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.addressbook.AddressbookContainer;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.MemoryFragmentPagerAdapter;
import android.support.v4.view.CustomViewPager;
import net.i2p.android.widget.SlidingTabLayout;
import net.i2p.router.RouterContext;
import java.io.File;
/**
* The main activity of the app. Contains a ViewPager that holds the three main
* views:
* <ul>
* <li>The console</li>
* <li>The addressbook</li>
* <li>The tunnel manager</li>
* </ul>
*/
public class I2PActivity extends I2PActivityBase implements
MainFragment.RouterControlListener {
CustomViewPager mViewPager;
ViewPagerAdapter mViewPagerAdapter;
SlidingTabLayout mSlidingTabLayout;
private boolean mAutoStartFromIntent = false;
private boolean _keep = true;
private boolean _startPressed = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewpager);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
mViewPager = (CustomViewPager) findViewById(R.id.pager);
mViewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
mViewPager.setAdapter(mViewPagerAdapter);
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
// Center the tabs in the layout
mSlidingTabLayout.setDistributeEvenly(true);
// Customize tab color
mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
@Override
public int getIndicatorColor(int position) {
return getResources().getColor(R.color.accent);
}
});
// Give the SlidingTabLayout the ViewPager
mSlidingTabLayout.setViewPager(mViewPager);
_keep = true;
}
public static class ViewPagerAdapter extends MemoryFragmentPagerAdapter {
private static final int NUM_ITEMS = 3;
private Context mContext;
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new ConsoleContainer();
case 1:
return new TunnelsContainer();
case 2:
return new AddressbookContainer();
default:
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return mContext.getString(R.string.label_console);
case 1:
return mContext.getString(R.string.label_tunnels);
case 2:
return mContext.getString(R.string.label_addresses);
default:
return null;
}
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
Util.d("Initializing...");
InitActivities init = new InitActivities(this);
init.debugStuff();
init.initialize();
super.onPostCreate(savedInstanceState);
handleIntents();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntents();
}
private void handleIntents() {
if (getIntent() == null)
return;
Intent intent = getIntent();
String action = intent.getAction();
if (action == null)
return;
switch (action) {
case "net.i2p.android.router.START_I2P":
if (mViewPager.getCurrentItem() != 0)
mViewPager.setCurrentItem(0, false);
autoStart();
break;
case Intent.ACTION_PICK:
mViewPager.setFixedPage(2, R.string.select_an_address);
break;
}
}
private void autoStart() {
if (canStart()) {
if (Connectivity.isConnected(this)) {
mAutoStartFromIntent = true;
onStartRouterClicked();
} else {
// Not connected to a network
// TODO: Notify user
}
} else {
// TODO: Notify user
}
}
@Override
public void onStart() {
super.onStart();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
}
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (_startPressed && Util.getRouterContext() != null)
_startPressed = false;
// Update menus, FAMs etc.
supportInvalidateOptionsMenu();
// Update main paging state
mViewPager.setPagingEnabled(!(Util.isStopping(state) || Util.isStopped(state)));
// If I2P was started by another app and is running, return to that app
if (state == State.RUNNING && mAutoStartFromIntent) {
I2PActivity.this.setResult(RESULT_OK);
finish();
}
}
};
@Override
public void onResume() {
super.onResume();
// Handle edge cases after shutting down router
mViewPager.updatePagingState();
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_base_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
case R.id.menu_help:
Intent hi = new Intent(this, HelpActivity.class);
switch (mViewPager.getCurrentItem()) {
case 1:
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
break;
case 2:
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
break;
}
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void onBackPressed() {
super.onBackPressed();
RouterContext ctx = Util.getRouterContext();
// RouterService svc = _routerService; Which is better to use?!
_keep = Connectivity.isConnected(this) && (ctx != null || _startPressed);
Util.d("*********************************************************");
Util.d("Back pressed, Keep? " + _keep);
Util.d("*********************************************************");
}
@Override
public void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateChange);
}
@Override
public void onDestroy() {
super.onDestroy();
if (!_keep) {
Thread t = new Thread(new KillMe());
t.start();
}
}
private class KillMe implements Runnable {
public void run() {
Util.d("*********************************************************");
Util.d("KillMe started!");
Util.d("*********************************************************");
try {
Thread.sleep(500); // is 500ms long enough?
} catch (InterruptedException ex) {
}
System.exit(0);
}
}
private boolean canStart() {
RouterService svc = _routerService;
return (svc == null) || (!_isBound) || svc.canManualStart();
}
private boolean canStop() {
RouterService svc = _routerService;
return svc != null && _isBound && svc.canManualStop();
}
// MainFragment.RouterControlListener
public boolean shouldShowOnOff() {
return (canStart() && Connectivity.isConnected(this)) || (canStop() && !isGracefulShutdownInProgress());
}
public boolean shouldBeOn() {
String action = getIntent().getAction();
return (canStop()) ||
(action != null && action.equals("net.i2p.android.router.START_I2P"));
}
public void onStartRouterClicked() {
_startPressed = true;
RouterService svc = _routerService;
if (svc != null && _isBound) {
setPref(PREF_AUTO_START, true);
svc.manualStart();
} else {
(new File(Util.getFileDir(this), "wrapper.log")).delete();
startRouter();
}
}
public boolean onStopRouterClicked() {
RouterService svc = _routerService;
if (svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.manualQuit();
return true;
}
return false;
}
/** @since 0.9.19 */
public boolean isGracefulShutdownInProgress() {
RouterService svc = _routerService;
return svc != null && svc.isGracefulShutdownInProgress();
}
/** @since 0.9.19 */
public boolean onGracefulShutdownClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.gracefulShutdown();
return true;
}
return false;
}
/** @since 0.9.19 */
public boolean onCancelGracefulShutdownClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.cancelGracefulShutdown();
return true;
}
return false;
}
}

View File

@ -0,0 +1,207 @@
package net.i2p.android;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import net.i2p.android.router.service.RouterBinder;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.LocaleManager;
public abstract class I2PActivityBase extends AppCompatActivity {
/**
* Router variables
*/
protected boolean _isBound;
protected boolean _triedBind;
protected ServiceConnection _connection;
protected RouterService _routerService;
private SharedPreferences _sharedPrefs;
private static final String SHARED_PREFS = "net.i2p.android.router";
protected static final String PREF_AUTO_START = "autoStart";
/**
* true leads to a poor install experience, very slow to paint the screen
*/
protected static final boolean DEFAULT_AUTO_START = false;
private final LocaleManager localeManager = new LocaleManager();
/**
* Called when the activity is first created.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
Util.d(this + " onCreate called");
localeManager.onCreate(this);
super.onCreate(savedInstanceState);
_sharedPrefs = getSharedPreferences(SHARED_PREFS, 0);
}
@Override
public void onRestart() {
Util.d(this + " onRestart called");
super.onRestart();
}
@Override
public void onStart() {
Util.d(this + " onStart called");
super.onStart();
if (_sharedPrefs.getBoolean(PREF_AUTO_START, DEFAULT_AUTO_START))
startRouter();
else
bindRouter(false);
}
/**
* @param def default
*/
public boolean getPref(String pref, boolean def) {
return _sharedPrefs.getBoolean(pref, def);
}
/**
* @param def default
*/
public String getPref(String pref, String def) {
return _sharedPrefs.getString(pref, def);
}
/**
* @return success
*/
public boolean setPref(String pref, boolean val) {
SharedPreferences.Editor edit = _sharedPrefs.edit();
edit.putBoolean(pref, val);
return edit.commit();
}
/**
* @return success
*/
public boolean setPref(String pref, String val) {
SharedPreferences.Editor edit = _sharedPrefs.edit();
edit.putString(pref, val);
return edit.commit();
}
@Override
public void onResume() {
Util.d(this + " onResume called");
super.onResume();
localeManager.onResume(this);
}
public void notifyLocaleChanged() {
localeManager.onResume(this);
}
@Override
public void onPause() {
Util.d(this + " onPause called");
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
Util.d(this + " onSaveInstanceState called");
super.onSaveInstanceState(outState);
}
@Override
public void onStop() {
Util.d(this + " onStop called");
unbindRouter();
super.onStop();
}
@Override
public void onDestroy() {
Util.d(this + " onDestroy called");
super.onDestroy();
}
////// Service stuff
/**
* Start the service and bind to it
*/
protected boolean startRouter() {
Intent intent = new Intent();
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
Util.d(this + " calling startService");
ComponentName name = startService(intent);
if (name == null)
Util.d(this + " XXXXXXXXXXXXXXXXXXXX got null from startService!");
Util.d(this + " got from startService: " + name);
boolean success = bindRouter(true);
if (!success)
Util.d(this + " Bind router failed");
return success;
}
/**
* Bind only
*/
protected boolean bindRouter(boolean autoCreate) {
Intent intent = new Intent(RouterBinder.class.getName());
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
Util.d(this + " calling bindService");
_connection = new RouterConnection();
_triedBind = bindService(intent, _connection, autoCreate ? BIND_AUTO_CREATE : 0);
Util.d(this + " bindService: auto create? " + autoCreate + " success? " + _triedBind);
return _triedBind;
}
protected void unbindRouter() {
Util.d(this + " unbindRouter called with _isBound:" + _isBound + " _connection:" + _connection + " _triedBind:" + _triedBind);
if (_triedBind && _connection != null)
unbindService(_connection);
_triedBind = false;
_connection = null;
_routerService = null;
_isBound = false;
}
/**
* Class for interacting with the main interface of the RouterService.
*/
protected class RouterConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
Util.d(this + " connected to router service");
RouterBinder binder = (RouterBinder) service;
RouterService svc = binder.getService();
_routerService = svc;
_isBound = true;
onRouterBind(svc);
}
public void onServiceDisconnected(ComponentName name) {
Util.d(this + " disconnected from router service!!!!!!!");
// save memory
_routerService = null;
_isBound = false;
onRouterUnbind();
}
}
/**
* callback from ServiceConnection, override as necessary
*/
protected void onRouterBind(RouterService svc) {
}
/**
* callback from ServiceConnection, override as necessary
*/
protected void onRouterUnbind() {
}
}

View File

@ -1,9 +1,10 @@
package net.i2p.android.router;
package net.i2p.android;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
@ -93,15 +94,23 @@ class InitActivities {
docsDir.mkdir();
copyResourceToFile(R.raw.ahelper_conflict_header_ht, "docs/ahelper-conflict-header.ht");
copyResourceToFile(R.raw.ahelper_new_header_ht, "docs/ahelper-new-header.ht");
copyResourceToFile(R.raw.ahelper_notfound_header_ht, "docs/ahelper-notfound-header.ht");
copyResourceToFile(R.raw.auth_header_ht, "docs/auth-header.ht");
copyResourceToFile(R.raw.baduri_header_ht, "docs/baduri-header.ht");
copyResourceToFile(R.raw.denied_header_ht, "docs/denied-header.ht");
copyResourceToFile(R.raw.dnf_header_ht, "docs/dnf-header.ht");
copyResourceToFile(R.raw.dnfb_header_ht, "docs/dnfb-header.ht");
copyResourceToFile(R.raw.dnfh_header_ht, "docs/dnfh-header.ht");
copyResourceToFile(R.raw.dnfp_header_ht, "docs/dnfp-header.ht");
copyResourceToFile(R.raw.enc_header_ht, "docs/enc-header.ht");
copyResourceToFile(R.raw.encp_header_ht, "docs/encp-header.ht");
copyResourceToFile(R.raw.localhost_header_ht, "docs/localhost-header.ht");
copyResourceToFile(R.raw.nols_header_ht, "docs/nols-header.ht");
copyResourceToFile(R.raw.nolsp_header_ht, "docs/nolsp-header.ht");
copyResourceToFile(R.raw.noproxy_header_ht, "docs/noproxy-header.ht");
copyResourceToFile(R.raw.protocol_header_ht, "docs/protocol-header.ht");
copyResourceToFile(R.raw.reset_header_ht, "docs/reset-header.ht");
copyResourceToFile(R.raw.resetp_header_ht, "docs/resetp-header.ht");
File cssDir = new File(docsDir, "themes/console/light");
cssDir.mkdirs();
@ -124,8 +133,7 @@ class InitActivities {
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];
for (File f : allcertificates) {
Util.d("Deleting old certificate file/dir " + f);
FileUtil.rmdir(f, false);
}

View File

@ -1,22 +1,40 @@
package net.i2p.android.apps;
import java.io.File;
import android.content.Context;
import net.i2p.android.router.NewsActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Notifications;
import net.i2p.crypto.SU3File;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.news.NewsEntry;
import net.i2p.router.news.NewsMetadata;
import net.i2p.router.news.NewsXMLParser;
import net.i2p.router.util.RFC822Date;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.Translate;
import net.i2p.util.ReusableGZIPInputStream;
import net.i2p.util.SecureFileOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Date;
import java.util.List;
/**
* From router console, simplified since we don't deal with router versions
* or updates.
*/
public class NewsFetcher implements Runnable, EepGet.StatusListener {
private final Context mCtx;
private final RouterContext _context;
private final Notifications _notif;
private final Log _log;
@ -35,10 +53,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public static /* final */ synchronized NewsFetcher getInstance(
RouterContext ctx, Notifications notif) {
Context context, RouterContext ctx, Notifications notif) {
if (_instance != null)
return _instance;
_instance = new NewsFetcher(ctx, notif);
_instance = new NewsFetcher(context, ctx, notif);
return _instance;
}
@ -47,20 +65,21 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private static final String TEMP_NEWS_FILE = "news.xml.temp";
/**
* 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.
* 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
* @since 0.7.14 not configurable
*/
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml";
private static final String BACKUP_NEWS_URL_SU3 = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.su3";
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
private static final String DEFAULT_REFRESH_FREQUENCY = 24 * 60 * 60 * 1000 + "";
private static final String PROP_NEWS_URL = "router.newsURL";
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
public static final String DEFAULT_NEWS_URL_SU3 = "http://echelon.i2p/news/news.su3";
private NewsFetcher(RouterContext ctx, Notifications notif) {
private NewsFetcher(Context context, RouterContext ctx, Notifications notif) {
mCtx = context;
_context = ctx;
_notif = notif;
_context.addShutdownTask(new Shutdown());
@ -69,7 +88,8 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
String last = ctx.getProperty(PROP_LAST_CHECKED);
if (last != null)
_lastFetch = Long.parseLong(last);
} catch (NumberFormatException nfe) {}
} catch (NumberFormatException nfe) {
}
File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
// isn't already there on android
newsDir.mkdir();
@ -94,24 +114,24 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public String status() {
StringBuilder buf = new StringBuilder(128);
long now = _context.clock().now();
if (_lastUpdated > 0) {
buf.append(Translate.getString("News last updated {0} ago.",
DataHelper.formatDuration2(now - _lastUpdated),
_context, "foo"))
.append('\n');
}
if (_lastFetch > _lastUpdated) {
buf.append(Translate.getString("News last checked {0} ago.",
DataHelper.formatDuration2(now - _lastFetch),
_context, "foo"));
}
return buf.toString();
StringBuilder buf = new StringBuilder(128);
long now = _context.clock().now();
if (_lastUpdated > 0) {
buf.append(mCtx.getString(R.string.news_last_updated,
DataHelper.formatDuration2(now - _lastUpdated)))
.append('\n');
}
if (_lastFetch > _lastUpdated) {
buf.append(mCtx.getString(R.string.news_last_checked,
DataHelper.formatDuration2(now - _lastFetch)));
}
return buf.toString();
}
private static final long INITIAL_DELAY = 5*60*1000;
private static final long RUN_DELAY = 30*60*1000;
// Runnable
private static final long INITIAL_DELAY = 5 * 60 * 1000;
private static final long RUN_DELAY = 30 * 60 * 1000;
public void run() {
_thread = Thread.currentThread();
@ -137,7 +157,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
return true;
updateLastFetched();
String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
DEFAULT_REFRESH_FREQUENCY);
DEFAULT_REFRESH_FREQUENCY);
try {
long ms = Long.parseLong(freq);
if (ms <= 0)
@ -158,8 +178,9 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
/**
* Call this when changing news URLs to force an update next time the timer fires.
* @since 0.8.7
* Call this when changing news URLs to force an update next time the timer fires.
*
* @since 0.8.7
*/
void invalidateNews() {
_lastModified = null;
@ -167,7 +188,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public void fetchNews() {
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL);
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL_SU3);
String proxyHost = "127.0.0.1";
int proxyPort = 4444;
if (_tempFile.exists())
@ -183,7 +204,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} else {
// backup news location - always proxied
_tempFile.delete();
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL_SU3, true, null, _lastModified);
get.addStatusListener(this);
if (get.fetch())
_lastModified = get.getLastModified();
@ -193,25 +214,38 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
// EepGet.StatusListener
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
// ignore
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
_log.info("News fetched from " + url + " with " + (alreadyTransferred + bytesTransferred));
long now = _context.clock().now();
if (_tempFile.exists()) {
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
if (_tempFile.exists() && _tempFile.length() > 0) {
File from;
if (url.endsWith(".su3")) {
try {
from = processSU3();
} catch (IOException ioe) {
_log.error("Failed to extract the news file", ioe);
_tempFile.delete();
return;
}
} else {
from = _tempFile;
}
boolean copied = FileUtil.rename(from, _newsFile);
_tempFile.delete();
if (copied) {
_lastUpdated = now;
_tempFile.delete();
// Notify user
_notif.notify("News Updated", "Touch to view latest I2P news",
_notif.notify(mCtx.getString(R.string.news_updated),
mCtx.getString(R.string.view_news),
NewsActivity.class);
} else {
if (_log.shouldLog(Log.ERROR))
@ -226,13 +260,21 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_context.router().saveConfig();
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (_log.shouldLog(Log.WARN))
_log.warn("Failed to fetch the news from " + url);
_tempFile.delete();
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
public void headerReceived(String url, int attemptNum, String key, String val) {
}
public void attempting(String url) {
}
private class Shutdown implements Runnable {
public void run() {
@ -241,4 +283,138 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_thread.interrupt();
}
}
//
// SU3 handlers
//
/**
* Process the fetched su3 news file _tempFile.
* Handles 3 types of contained files: xml.gz (preferred), xml, and html (old format fake xml)
*
* @return the temp file contining the HTML-format news.xml
* @since 0.9.20
*/
private File processSU3() throws IOException {
SU3File su3 = new SU3File(_context, _tempFile);
// real xml, maybe gz, maybe not
File to1 = new File(_context.getTempDir(), "tmp-" + _context.random().nextInt() + ".xml");
// real xml
File to2 = new File(_context.getTempDir(), "tmp2-" + _context.random().nextInt() + ".xml");
try {
su3.verifyAndMigrate(to1);
int type = su3.getFileType();
if (su3.getContentType() != SU3File.CONTENT_NEWS)
throw new IOException("bad content type: " + su3.getContentType());
if (type == SU3File.TYPE_HTML)
return to1;
if (type != SU3File.TYPE_XML && type != SU3File.TYPE_XML_GZ)
throw new IOException("bad file type: " + type);
File xml;
if (type == SU3File.TYPE_XML_GZ) {
gunzip(to1, to2);
xml = to2;
to1.delete();
} else {
xml = to1;
}
NewsXMLParser parser = new NewsXMLParser(_context);
parser.parse(xml);
xml.delete();
NewsMetadata data = parser.getMetadata();
List<NewsEntry> entries = parser.getEntries();
String sudVersion = su3.getVersionString();
String signingKeyName = su3.getSignerString();
File to3 = new File(_context.getTempDir(), "tmp3-" + _context.random().nextInt() + ".xml");
outputOldNewsXML(data, entries, sudVersion, signingKeyName, to3);
return to3;
} finally {
to2.delete();
}
}
/**
* Gunzip the file
*
* @since 0.9.20
*/
private static void gunzip(File from, File to) throws IOException {
ReusableGZIPInputStream in = ReusableGZIPInputStream.acquire();
OutputStream out = null;
try {
in.initialize(new FileInputStream(from));
out = new SecureFileOutputStream(to);
byte buf[] = new byte[4096];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
ReusableGZIPInputStream.release(in);
}
}
/**
* Output in the old format.
*
* @since 0.9.20
*/
private void outputOldNewsXML(NewsMetadata data, List<NewsEntry> entries,
String sudVersion, String signingKeyName, File to) throws IOException {
NewsMetadata.Release latestRelease = data.releases.get(0);
Writer out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(to), "UTF-8"));
out.write("<!--\n");
// update metadata in old format
out.write("<i2p.release ");
if (latestRelease.i2pVersion != null)
out.write(" version=\"" + latestRelease.i2pVersion + '"');
if (latestRelease.minVersion != null)
out.write(" minVersion=\"" + latestRelease.minVersion + '"');
if (latestRelease.minJavaVersion != null)
out.write(" minJavaVersion=\"" + latestRelease.minJavaVersion + '"');
String su3Torrent = "";
String su2Torrent = "";
for (NewsMetadata.Update update : latestRelease.updates) {
if (update.torrent != null) {
if ("su3".equals(update.type))
su3Torrent = update.torrent;
else if ("su2".equals(update.type))
su2Torrent = update.torrent;
}
}
if (!su2Torrent.isEmpty())
out.write(" su2Torrent=\"" + su2Torrent + '"');
if (!su3Torrent.isEmpty())
out.write(" su3Torrent=\"" + su3Torrent + '"');
out.write("/>\n");
// su3 and feed metadata for debugging
out.write("** News version:\t" + DataHelper.stripHTML(sudVersion) + '\n');
out.write("** Signed by:\t" + signingKeyName + '\n');
out.write("** Feed:\t" + DataHelper.stripHTML(data.feedTitle) + '\n');
out.write("** Feed ID:\t" + DataHelper.stripHTML(data.feedID) + '\n');
out.write("** Feed Date:\t" + (new Date(data.feedUpdated)) + '\n');
out.write("-->\n");
if (entries == null)
return;
for (NewsEntry e : entries) {
if (e.title == null || e.content == null)
continue;
out.write("<!-- Entry Date: " + (new Date(e.updated)) + " -->\n");
out.write("<h3>");
out.write(e.title);
out.write("</h3>\n");
out.write(e.content);
out.write("\n\n");
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
}
}
}

View File

@ -3,6 +3,7 @@ package net.i2p.android.help;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
public class Browser implements Comparable<Browser> {
public final String packageName;
@ -55,7 +56,7 @@ public class Browser implements Comparable<Browser> {
}
@Override
public int compareTo(Browser browser) {
public int compareTo(@NonNull Browser browser) {
// Sort order: supported -> unknown -> unsupported
int a = getOrder(this);
int b = getOrder(browser);

View File

@ -35,8 +35,8 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
}
}
public static interface OnBrowserSelectedListener {
public void onBrowserSelected(Browser browser);
public interface OnBrowserSelectedListener {
void onBrowserSelected(Browser browser);
}
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {

View File

@ -1,15 +1,16 @@
package net.i2p.android.help;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import net.i2p.android.router.R;
import net.i2p.android.util.LocaleManager;
import java.lang.reflect.Field;
public class BrowserConfigActivity extends ActionBarActivity implements
public class BrowserConfigActivity extends AppCompatActivity implements
BrowserAdapter.OnBrowserSelectedListener {
/**
@ -18,8 +19,11 @@ public class BrowserConfigActivity extends ActionBarActivity implements
*/
private boolean mTwoPane;
private final LocaleManager localeManager = new LocaleManager();
@Override
public void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
@ -43,6 +47,12 @@ public class BrowserConfigActivity extends ActionBarActivity implements
}
}
@Override
public void onResume() {
super.onResume();
localeManager.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

View File

@ -19,6 +19,7 @@ import android.view.ViewGroup;
import net.i2p.android.router.R;
import net.i2p.android.router.util.BetterAsyncTaskLoader;
import net.i2p.android.widget.DividerItemDecoration;
import java.lang.reflect.Field;
import java.util.ArrayList;
@ -55,6 +56,9 @@ public class BrowserListFragment extends Fragment implements
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
// use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
@ -102,12 +106,12 @@ public class BrowserListFragment extends Fragment implements
@Override
public List<Browser> loadInBackground() {
List<Browser> browsers = new ArrayList<Browser>();
Map<String, String> recommendedMap = new HashMap<String, String>();
List<Browser> browsers = new ArrayList<>();
Map<String, String> recommendedMap = new HashMap<>();
for (int i = 0; i < recommended.size(); i++) {
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
}
Map<String, String> supportedMap = new HashMap<String, String>();
Map<String, String> supportedMap = new HashMap<>();
for (int i = 0; i < supported.size(); i++) {
supportedMap.put(supported.get(i), supportedLabels.get(i));
}

View File

@ -2,7 +2,9 @@ package net.i2p.android.help;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
@ -10,8 +12,9 @@ import android.view.MenuItem;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.util.LocaleManager;
public class HelpActivity extends ActionBarActivity implements
public class HelpActivity extends AppCompatActivity implements
HelpListFragment.OnEntrySelectedListener {
public static final String CATEGORY = "help_category";
public static final int CAT_MAIN = 0;
@ -24,9 +27,13 @@ public class HelpActivity extends ActionBarActivity implements
* device.
*/
private boolean mTwoPane;
private int mCategory;
private final LocaleManager localeManager = new LocaleManager();
@Override
public void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
@ -49,9 +56,16 @@ public class HelpActivity extends ActionBarActivity implements
.commit();
}
int category = getIntent().getIntExtra(CATEGORY, -1);
if (category >= 0)
showCategory(category);
mCategory = getIntent().getIntExtra(CATEGORY, -1);
if (mCategory >= 0) {
showCategory(mCategory);
}
}
@Override
public void onResume() {
super.onResume();
localeManager.onResume(this);
}
@Override
@ -64,7 +78,24 @@ public class HelpActivity extends ActionBarActivity implements
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
if (mCategory >= 0) {
onBackPressed();
} else {
Intent upIntent = NavUtils.getParentActivityIntent(this);
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is NOT part of this app's task, so create a new task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
// Add all of this activity's parents to the back stack
.addNextIntentWithParentStack(upIntent)
// Navigate up to the closest parent
.startActivities();
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
}
return true;
case R.id.menu_help_licenses:
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
@ -84,6 +115,13 @@ public class HelpActivity extends ActionBarActivity implements
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (mCategory >= 0)
mCategory = -1;
}
// HelpListFragment.OnEntrySelectedListener
@Override
@ -91,8 +129,10 @@ public class HelpActivity extends ActionBarActivity implements
if (entry == CAT_CONFIGURE_BROWSER) {
Intent i = new Intent(this, BrowserConfigActivity.class);
startActivity(i);
} else
} else {
mCategory = entry;
showCategory(entry);
}
}
private void showCategory(int category) {

View File

@ -14,7 +14,7 @@ public class HelpListFragment extends ListFragment {
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(int entry);
void onEntrySelected(int entry);
}
@Override

View File

@ -1,25 +1,63 @@
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.v4.app.ActivityCompat;
import android.support.v7.widget.Toolbar;
import android.view.View;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.i2ptunnel.preferences.EditTunnelActivity;
import net.i2p.android.router.R;
public class TunnelDetailActivity extends I2PActivityBase implements
TunnelDetailFragment.OnTunnelDeletedListener {
TunnelDetailFragment.TunnelDetailListener {
private boolean transitionReversed;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
transitionReversed = 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();
.add(android.R.id.content, detailFrag).commit();
}
}
// TunnelDetailFragment.OnTunnelDeletedListener
@Override
public void onStart() {
super.onStart();
Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar);
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
}
@Override
public void finish() {
if (transitionReversed)
super.finish();
else {
transitionReversed = true;
ActivityCompat.finishAfterTransition(this);
}
}
// TunnelDetailFragment.TunnelDetailListener
@Override
public void onEditTunnel(int tunnelId) {
Intent editIntent = new Intent(this, EditTunnelActivity.class);
editIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
startActivity(editIntent);
}
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
finish();

View File

@ -1,20 +1,23 @@
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.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -22,12 +25,22 @@ import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.android.util.FragmentUtils;
import net.i2p.app.ClientAppState;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.List;
public class TunnelDetailFragment extends Fragment {
public static final String TUNNEL_ID = "tunnel_id";
OnTunnelDeletedListener mCallback;
TunnelDetailListener mCallback;
private TunnelControllerGroup mGroup;
private TunnelEntry mTunnel;
private Toolbar mToolbar;
public static TunnelDetailFragment newInstance(int tunnelId) {
TunnelDetailFragment f = new TunnelDetailFragment();
@ -38,8 +51,9 @@ public class TunnelDetailFragment extends Fragment {
}
// Container Activity must implement this interface
public interface OnTunnelDeletedListener {
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
public interface TunnelDetailListener {
void onEditTunnel(int tunnelId);
void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
}
@Override
@ -48,19 +62,15 @@ public class TunnelDetailFragment extends Fragment {
// 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");
}
mCallback = FragmentUtils.getParent(this, TunnelDetailListener.class);
if (mCallback == null)
throw new ClassCastException("Parent must implement TunnelDetailListener");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
String error;
try {
@ -86,24 +96,86 @@ public class TunnelDetailFragment extends Fragment {
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
mToolbar = (Toolbar) v.findViewById(R.id.detail_toolbar);
mToolbar.inflateMenu(R.menu.fragment_i2ptunnel_detail_actions);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return onToolbarItemSelected(menuItem);
}
});
updateToolbar();
if (mTunnel != null) {
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(mTunnel.getName());
ViewCompat.setTransitionName(name,
getActivity().getString(R.string.TUNNEL_NAME) + mTunnel.getId());
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());
ViewCompat.setTransitionName(description,
getActivity().getString(R.string.TUNNEL_DESCRIPTION) + mTunnel.getId());
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
details.setText(mTunnel.getDetails());
if (!mTunnel.getDetails().isEmpty()) {
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
View copyDetails = v.findViewById(R.id.tunnel_details_copy);
details.setText(mTunnel.getDetails());
if (!mTunnel.isClient()) {
copyDetails.setVisibility(View.VISIBLE);
copyDetails.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
copyToClipbardLegacy();
else
copyToClipboardHoneycomb();
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
targetIfacePort.setText(mTunnel.getTunnelLink(false));
Toast.makeText(getActivity(), R.string.address_copied_to_clipboard, Toast.LENGTH_SHORT).show();
}
});
}
}
View accessIfacePortItem = v.findViewById(R.id.tunnel_access_interface_port_item);
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
accessIfacePort.setText(mTunnel.getTunnelLink(false));
View accessIfaceOpen = v.findViewById(R.id.tunnel_access_open);
View targetIfacePortItem = v.findViewById(R.id.tunnel_target_interface_port_item);
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
View targetIfaceOpen = v.findViewById(R.id.tunnel_target_open);
switch (mTunnel.getInternalType()) {
case "httpbidirserver":
accessIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(accessIfaceOpen, true);
v.findViewById(R.id.icon_link_access).setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(targetIfaceOpen, false);
break;
case "streamrserver":
accessIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(accessIfaceOpen, true);
targetIfacePortItem.setVisibility(View.GONE);
break;
case "streamrclient":
accessIfacePortItem.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(targetIfaceOpen, false);
break;
default:
if (mTunnel.isClient()) {
accessIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(accessIfaceOpen, true);
targetIfacePortItem.setVisibility(View.GONE);
} else {
accessIfacePortItem.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(targetIfaceOpen, false);
}
}
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
autoStart.setChecked(mTunnel.startAutomatically());
@ -112,19 +184,50 @@ public class TunnelDetailFragment extends Fragment {
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);
private void setupOpen(View open, final boolean client) {
if (mTunnel.isRunning() &&
(client ? mTunnel.isClientLinkValid() : mTunnel.isServerLinkValid())) {
open.setVisibility(View.VISIBLE);
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(client ? mTunnel.getClientLink(true) : mTunnel.getServerLink(true)));
try {
startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = mTunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
}
})
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
}
}
});
} else
open.setVisibility(View.GONE);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
private void updateToolbar() {
Menu menu = mToolbar.getMenu();
MenuItem start = menu.findItem(R.id.action_start_tunnel);
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
if (mTunnel != null) {
if (mTunnel != null && mGroup != null &&
(mGroup.getState() == ClientAppState.STARTING ||
mGroup.getState() == ClientAppState.RUNNING)) {
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
start.setVisible(isStopped);
@ -141,8 +244,10 @@ public class TunnelDetailFragment extends Fragment {
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
private boolean onToolbarItemSelected(MenuItem item) {
if (mTunnel == null)
return false;
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_start_tunnel:
@ -150,21 +255,23 @@ public class TunnelDetailFragment extends Fragment {
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();
// Reload the toolbar to change the start/stop action
updateToolbar();
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();
// Reload the toolbar to change the start/stop action
updateToolbar();
return true;
case R.id.action_edit_tunnel:
mCallback.onEditTunnel(mTunnel.getId());
return true;
case R.id.action_delete_tunnel:
DialogFragment dg = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
@ -174,7 +281,8 @@ public class TunnelDetailFragment extends Fragment {
public void onClick(DialogInterface dialog, int which) {
List<String> msgs = TunnelUtil.deleteTunnel(
getActivity(), mGroup, mTunnel.getId());
I2PAppContext.getGlobalContext(),
mGroup, mTunnel.getId(), null);
dialog.dismiss();
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
@ -192,4 +300,17 @@ public class TunnelDetailFragment extends Fragment {
return super.onOptionsItemSelected(item);
}
}
private void copyToClipbardLegacy() {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mTunnel.getDetails());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void copyToClipboardHoneycomb() {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
mTunnel.getName(), mTunnel.getDetails());
clipboard.setPrimaryClip(clip);
}
}

View File

@ -1,18 +1,20 @@
package net.i2p.android.i2ptunnel;
import java.util.List;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.Toast;
import net.i2p.android.i2ptunnel.util.TunnelConfig;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import java.util.List;
public class TunnelEntry {
public static final int RUNNING = 1;
@ -30,7 +32,7 @@ public class TunnelEntry {
TunnelConfig cfg) {
int tunnelId = tcg.getControllers().size();
List<String> msgs = TunnelUtil.saveTunnel(
ctx, tcg, -1, cfg.getConfig());
I2PAppContext.getGlobalContext(), tcg, -1, cfg);
// TODO: Do something else with the other messages.
Toast.makeText(ctx.getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
@ -250,7 +252,7 @@ public class TunnelEntry {
if (isClient())
details = getClientDestination();
else
details = "";
details = getDestHashBase32();
return details;
}

View File

@ -1,94 +1,185 @@
package net.i2p.android.i2ptunnel;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.util.FragmentUtils;
import java.util.List;
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
private final LayoutInflater mInflater;
public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mCtx;
private boolean mClientTunnels;
private TunnelListFragment.OnTunnelSelectedListener mListener;
private FragmentUtils.TwoPaneProvider mTwoPane;
private List<TunnelEntry> mTunnels;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = -1;
public TunnelEntryAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
public void setData(List<TunnelEntry> tunnels) {
clear();
if (tunnels != null) {
for (TunnelEntry tunnel : tunnels) {
add(tunnel);
}
public static class TunnelViewHolder extends RecyclerView.ViewHolder {
public ImageView status;
public TextView name;
public TextView description;
public TextView interfacePort;
public TunnelViewHolder(View itemView) {
super(itemView);
status = (ImageView) itemView.findViewById(R.id.tunnel_status);
name = (TextView) itemView.findViewById(R.id.tunnel_name);
description = (TextView) itemView.findViewById(R.id.tunnel_description);
interfacePort = (TextView) itemView.findViewById(R.id.tunnel_interface_port);
}
}
public TunnelEntryAdapter(Context context, boolean clientTunnels,
TunnelListFragment.OnTunnelSelectedListener listener,
FragmentUtils.TwoPaneProvider twoPane) {
super();
mCtx = context;
mClientTunnels = clientTunnels;
mListener = listener;
mTwoPane = twoPane;
}
public void setTunnels(List<TunnelEntry> tunnels) {
mTunnels = tunnels;
notifyDataSetChanged();
}
public void addTunnel(TunnelEntry tunnel) {
mTunnels.add(tunnel);
notifyItemInserted(mTunnels.size()-1);
}
public TunnelEntry getTunnel(int position) {
if (position < 0)
return null;
return mTunnels.get(position);
}
public void setActivatedPosition(int position) {
mActivatedPosition = position;
}
public int getActivatedPosition() {
return mActivatedPosition;
}
public void clearActivatedPosition() {
mActivatedPosition = -1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
final TunnelEntry tunnel = getItem(position);
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
status.setImageDrawable(tunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
status.setBackgroundDrawable(tunnel.getStatusBackground());
public int getItemViewType(int position) {
if (mTunnels == null)
return R.string.router_not_running;
else if (mTunnels.isEmpty())
return R.layout.listitem_empty;
else
status.setBackground(tunnel.getStatusBackground());
return R.layout.listitem_i2ptunnel;
}
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(tunnel.getName());
// Create new views (invoked by the layout manager)
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int vt = viewType;
if (viewType == R.string.router_not_running)
vt = R.layout.listitem_empty;
TextView type = (TextView) v.findViewById(R.id.tunnel_description);
type.setText(tunnel.getDescription());
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
ifacePort.setText(tunnel.getTunnelLink(false));
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) {
View open = v.findViewById(R.id.tunnel_open);
open.setVisibility(View.VISIBLE);
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
try {
getContext().startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = tunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
getContext().startActivity(intent);
}
}
})
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
}
}
});
View v = LayoutInflater.from(parent.getContext())
.inflate(vt, parent, false);
switch (viewType) {
case R.layout.listitem_i2ptunnel:
return new TunnelViewHolder(v);
default:
return new SimpleViewHolder(v);
}
}
return v;
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case R.string.router_not_running:
((TextView) holder.itemView).setText(
mCtx.getString(R.string.router_not_running));
break;
case R.layout.listitem_empty:
((TextView) holder.itemView).setText(mClientTunnels ?
R.string.no_configured_client_tunnels :
R.string.no_configured_server_tunnels);
break;
case R.layout.listitem_i2ptunnel:
final TunnelViewHolder tvh = (TunnelViewHolder) holder;
final TunnelEntry tunnel = getTunnel(position);
tvh.status.setImageDrawable(tunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
tvh.status.setBackgroundDrawable(tunnel.getStatusBackground());
else
tvh.status.setBackground(tunnel.getStatusBackground());
tvh.name.setText(tunnel.getName());
tvh.description.setText(tunnel.getDescription());
tvh.interfacePort.setText(tunnel.getTunnelLink(false));
ViewCompat.setTransitionName(tvh.name,
mCtx.getString(R.string.TUNNEL_NAME) + tunnel.getId());
ViewCompat.setTransitionName(tvh.description,
mCtx.getString(R.string.TUNNEL_DESCRIPTION) + tunnel.getId());
tvh.itemView.setSelected(mTwoPane.isTwoPane() && position == mActivatedPosition);
tvh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int oldPosition = mActivatedPosition;
mActivatedPosition = position;
notifyItemChanged(oldPosition);
notifyItemChanged(position);
Pair<View, String> namePair = Pair.create(
(View)tvh.name,
ViewCompat.getTransitionName(tvh.name));
Pair<View, String> descPair = Pair.create(
(View)tvh.description,
ViewCompat.getTransitionName(tvh.description));
Pair<View, String>[] pairs = new Pair[]{ namePair, descPair};
mListener.onTunnelSelected(tunnel.getId(), pairs);
}
});
break;
default:
break;
}
}
// Return the size of the dataset (invoked by the layout manager)
@Override
public int getItemCount() {
if (mTunnels == null || mTunnels.isEmpty())
return 1;
return mTunnels.size();
}
}

View File

@ -1,15 +1,17 @@
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;
import net.i2p.android.router.util.Util;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.List;
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
private TunnelControllerGroup mGroup;
private boolean mClientTunnels;
@ -26,7 +28,13 @@ public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
@Override
public List<TunnelEntry> loadInBackground() {
List<TunnelEntry> ret = new ArrayList<TunnelEntry>();
// Don't load tunnels if the router is not running
// TODO: in future we might be able to view and edit tunnels while router is not running
RouterContext routerContext = Util.getRouterContext();
if (routerContext == null)
return null;
List<TunnelEntry> ret = new ArrayList<>();
List<TunnelController> controllers = mGroup.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);

View File

@ -1,125 +0,0 @@
package net.i2p.android.i2ptunnel;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
public class TunnelListActivity extends I2PActivityBase implements
TunnelListFragment.OnTunnelSelectedListener,
TunnelDetailFragment.OnTunnelDeletedListener {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private static final String SELECTED_PAGE = "selected_page";
private static final int PAGE_CLIENT = 0;
private Spinner mSpinner;
@Override
protected boolean canUseTwoPanes() {
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
R.array.i2ptunnel_pages, android.R.layout.simple_spinner_dropdown_item));
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selectPage(i);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState != null) {
int selected = savedInstanceState.getInt(SELECTED_PAGE);
mSpinner.setSelection(selected);
} else
selectPage(PAGE_CLIENT);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
}
private void selectPage(int page) {
TunnelListFragment f = new TunnelListFragment();
Bundle args = new Bundle();
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, page == PAGE_CLIENT);
f.setArguments(args);
// In two-pane mode, list items should be given the
// 'activated' state when touched.
if (mTwoPane)
f.setActivateOnItemClick(true);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f).commit();
}
// TunnelListFragment.OnTunnelSelectedListener
public void onTunnelSelected(int tunnelId) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, detailFrag).commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, TunnelDetailActivity.class);
detailIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
startActivity(detailIntent);
}
}
// TunnelDetailFragment.OnTunnelDeletedListener
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
// Should only get here in two-pane mode, but just to be safe:
if (mTwoPane) {
if (numTunnelsLeft > 0) {
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(
(tunnelId > 0 ? tunnelId - 1 : 0));
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, detailFrag).commit();
} else {
TunnelDetailFragment detailFrag = (TunnelDetailFragment) getSupportFragmentManager().findFragmentById(R.id.detail_fragment);
getSupportFragmentManager().beginTransaction()
.remove(detailFrag).commit();
}
}
}
}

View File

@ -1,39 +1,39 @@
package net.i2p.android.i2ptunnel;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.Pair;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.Toast;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.i2ptunnel.util.TunnelConfig;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import com.pnikosis.materialishprogress.ProgressWheel;
import net.i2p.android.router.R;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.FragmentUtils;
import net.i2p.android.widget.DividerItemDecoration;
import net.i2p.android.widget.LoadingRecyclerView;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.List;
public class TunnelListFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
public class TunnelListFragment extends Fragment implements
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;
@ -43,23 +43,25 @@ public class TunnelListFragment extends ListFragment implements
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
private boolean mOnActivityCreated;
RouterContextProvider mRouterContextProvider;
OnTunnelSelectedListener mCallback;
FragmentUtils.TwoPaneProvider mTwoPane;
private TunnelControllerGroup mGroup;
private LoadingRecyclerView mRecyclerView;
private TunnelEntryAdapter mAdapter;
private boolean mClientTunnels;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
private boolean mActivateOnItemClick = false;
private ImageButton mNewTunnel;
// Container Activity must implement this interface
public interface OnTunnelSelectedListener {
public void onTunnelSelected(int tunnelId);
void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs);
}
public static TunnelListFragment newInstance(boolean showClientTunnels) {
TunnelListFragment f = new TunnelListFragment();
Bundle args = new Bundle();
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, showClientTunnels);
f.setArguments(args);
return f;
}
@Override
@ -68,47 +70,23 @@ public class TunnelListFragment extends ListFragment implements
// 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");
}
mCallback = FragmentUtils.getParent(this, OnTunnelSelectedListener.class);
if (mCallback == null)
throw new ClassCastException("Parent must implement OnTunnelSelectedListener");
mTwoPane = FragmentUtils.getParent(this, FragmentUtils.TwoPaneProvider.class);
if (mTwoPane == null)
throw new ClassCastException("Parent must implement TwoPaneProvider");
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnTunnelSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnTunnelSelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Create the list fragment's content view by calling the super method
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_list, container, false);
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
listContainer.addView(listFragmentView);
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
mNewTunnel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
}
});
mRecyclerView = (LoadingRecyclerView) v.findViewById(R.id.list);
View empty = v.findViewById(R.id.empty);
ProgressWheel loading = (ProgressWheel) v.findViewById(R.id.loading);
mRecyclerView.setLoadingView(empty, loading);
return v;
}
@ -116,156 +94,116 @@ public class TunnelListFragment extends ListFragment implements
@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);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
// use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
// Set the adapter for the list view
mAdapter = new TunnelEntryAdapter(getActivity(), mClientTunnels, mCallback, mTwoPane);
mRecyclerView.setAdapter(mAdapter);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION))
mAdapter.setActivatedPosition(savedInstanceState
.getInt(STATE_ACTIVATED_POSITION));
else
setEmptyText(getResources().getString(
R.string.router_not_running));
mAdapter.clearActivatedPosition();
// Initialize the adapter in case the RouterService has not been created
if (Util.getRouterContext() == null)
mAdapter.setTunnels(null);
}
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();
@Override
public void onStart() {
super.onStart();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
}
private State lastRouterState = null;
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (lastRouterState == null || lastRouterState != state) {
updateState(state);
lastRouterState = state;
}
}
};
public void updateState(State state) {
if (state == State.STOPPING || state == State.STOPPED ||
state == State.MANUAL_STOPPING ||
state == State.MANUAL_STOPPED ||
state == State.MANUAL_QUITTING ||
state == State.MANUAL_QUITTED)
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
else
initTunnels();
}
private void initTunnels() {
if (mGroup == null) {
try {
mGroup = TunnelControllerGroup.getInstance();
} catch (IllegalArgumentException iae) {
Util.e("Could not load tunnels", iae);
mGroup = null;
}
}
if (mGroup == null) {
setEmptyText(error);
} else {
if (mClientTunnels)
setEmptyText("No configured client tunnels.");
else
setEmptyText("No configured server tunnels.");
setListShown(false);
if (mGroup != null && isAdded()) {
mRecyclerView.setLoading(true);
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());
public void onResume() {
super.onResume();
// Triggers loader init via updateState() if the router is running
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
int activatedPosition = mAdapter.getActivatedPosition();
if (activatedPosition >= 0) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
if (getRouterContext() == null) {
mNewTunnel.setVisibility(View.GONE);
menu.findItem(R.id.action_start_all_tunnels).setVisible(false);
menu.findItem(R.id.action_stop_all_tunnels).setVisible(false);
menu.findItem(R.id.action_restart_all_tunnels).setVisible(false);
}
public void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
List<String> msgs;
switch (item.getItemId()) {
case R.id.action_start_all_tunnels:
msgs = mGroup.startAllControllers();
break;
case R.id.action_stop_all_tunnels:
msgs = mGroup.stopAllControllers();
break;
case R.id.action_restart_all_tunnels:
msgs = mGroup.restartAllControllers();
break;
case R.id.action_i2ptunnel_help:
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
// TODO: Do something with the other messages
if (msgs.size() > 0)
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
return true;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TUNNEL_WIZARD_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
Bundle tunnelData = data.getExtras().getBundle(TUNNEL_WIZARD_DATA);
TunnelConfig cfg = 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();
public void addTunnel(TunnelEntry tunnelEntry) {
mAdapter.addTunnel(tunnelEntry);
}
// LoaderManager.LoaderCallbacks<List<TunnelEntry>>
@ -278,20 +216,17 @@ public class TunnelListFragment extends ListFragment implements
List<TunnelEntry> data) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
mAdapter.setTunnels(data);
}
}
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(null);
if (Util.getRouterContext() == null)
mAdapter.setTunnels(null);
else
mAdapter.setTunnels(new ArrayList<TunnelEntry>());
}
}
}

View File

@ -1,15 +1,17 @@
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.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
public class TunnelWizardActivity extends AbstractWizardActivity {
@Override
@ -20,6 +22,7 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
@ -29,7 +32,7 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
result.putExtra(TunnelListFragment.TUNNEL_WIZARD_DATA, mWizardModel.save());
result.putExtra(TunnelsContainer.TUNNEL_WIZARD_DATA, mWizardModel.save());
setResult(Activity.RESULT_OK, result);
dialog.dismiss();
finish();

View File

@ -0,0 +1,288 @@
package net.i2p.android.i2ptunnel;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.util.Pair;
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;
import android.widget.ImageButton;
import android.widget.Toast;
import com.viewpagerindicator.TitlePageIndicator;
import net.i2p.android.i2ptunnel.preferences.EditTunnelContainerFragment;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.FragmentUtils;
import net.i2p.app.ClientAppState;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import net.i2p.router.RouterContext;
import java.util.List;
public class TunnelsContainer extends Fragment implements
FragmentUtils.TwoPaneProvider,
TunnelListFragment.OnTunnelSelectedListener,
TunnelDetailFragment.TunnelDetailListener {
static final int TUNNEL_WIZARD_REQUEST = 1;
public static final String TUNNEL_WIZARD_DATA = "tunnel_wizard_data";
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
ViewPager mViewPager;
TitlePageIndicator mPageIndicator;
FragmentPagerAdapter mFragPagerAdapter;
private static final String FRAGMENT_CLIENT = "client_fragment";
private static final String FRAGMENT_SERVER = "server_fragment";
private static final int FRAGMENT_ID_CLIENT = 0;
private static final int FRAGMENT_ID_SERVER = 1;
TunnelListFragment mClientFrag;
TunnelListFragment mServerFrag;
private ImageButton mNewTunnel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.container_tunnels, container, false);
mViewPager = (ViewPager) v.findViewById(R.id.pager);
mPageIndicator = (TitlePageIndicator) v.findViewById(R.id.page_indicator);
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
if (v.findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w720dp). If this view
// is present, then the activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState != null) {
mClientFrag = (TunnelListFragment) getChildFragmentManager().getFragment(
savedInstanceState, FRAGMENT_CLIENT);
mServerFrag = (TunnelListFragment) getChildFragmentManager().getFragment(
savedInstanceState, FRAGMENT_SERVER);
}
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mFragPagerAdapter = new TunnelsPagerAdapter(getChildFragmentManager());
mViewPager.setAdapter(mFragPagerAdapter);
// Bind the page indicator to the pager.
mPageIndicator.setViewPager(mViewPager);
mNewTunnel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
}
});
}
public class TunnelsPagerAdapter extends FragmentPagerAdapter {
private static final int NUM_ITEMS = 2;
public TunnelsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case FRAGMENT_ID_CLIENT:
return (mClientFrag = TunnelListFragment.newInstance(true));
case FRAGMENT_ID_SERVER:
return (mServerFrag = TunnelListFragment.newInstance(false));
default:
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case FRAGMENT_ID_CLIENT:
return getActivity().getString(R.string.label_i2ptunnel_client);
case FRAGMENT_ID_SERVER:
return getActivity().getString(R.string.label_i2ptunnel_server);
default:
return null;
}
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
RouterContext rCtx = Util.getRouterContext();
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
boolean showActions = rCtx != null && tcg != null &&
(tcg.getState() == ClientAppState.STARTING ||
tcg.getState() == ClientAppState.RUNNING);
menu.findItem(R.id.action_start_all_tunnels).setVisible(showActions);
menu.findItem(R.id.action_stop_all_tunnels).setVisible(showActions);
menu.findItem(R.id.action_restart_all_tunnels).setVisible(showActions);
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
if (tcg == null)
return false;
// Handle presses on the action bar items
List<String> msgs;
switch (item.getItemId()) {
case R.id.action_start_all_tunnels:
msgs = tcg.startAllControllers();
break;
case R.id.action_stop_all_tunnels:
msgs = tcg.stopAllControllers();
break;
case R.id.action_restart_all_tunnels:
msgs = tcg.restartAllControllers();
break;
default:
return super.onOptionsItemSelected(item);
}
// TODO: Do something with the other messages
if (msgs.size() > 0)
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
return true;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TUNNEL_WIZARD_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
Bundle tunnelData = data.getExtras().getBundle(TUNNEL_WIZARD_DATA);
// TODO fetch earlier
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
if (tunnel.isClient() && mClientFrag != null)
mClientFrag.addTunnel(tunnel);
else if (mServerFrag != null)
mServerFrag.addTunnel(tunnel);
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Since the pager fragments don't have known tags or IDs, the only way to persist the
// reference is to use putFragment/getFragment. Remember, we're not persisting the exact
// Fragment instance. This mechanism simply gives us a way to persist access to the
// 'current' fragment instance for the given fragment (which changes across orientation
// changes).
//
// The outcome of all this is that the "Refresh" menu button refreshes the stream across
// orientation changes.
if (mClientFrag != null)
getChildFragmentManager().putFragment(outState, FRAGMENT_CLIENT, mClientFrag);
if (mServerFrag != null)
getChildFragmentManager().putFragment(outState, FRAGMENT_SERVER, mServerFrag);
}
// FragmentUtils.TwoPaneProvider
public boolean isTwoPane() {
return mTwoPane;
}
// TunnelListFragment.OnTunnelSelectedListener
public final void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
getChildFragmentManager().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(getActivity(), TunnelDetailActivity.class);
detailIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
getActivity(), pairs);
ActivityCompat.startActivity(getActivity(), detailIntent, options.toBundle());
}
}
// TunnelDetailFragment.TunnelDetailListener
@Override
public void onEditTunnel(int tunnelId) {
Fragment editFrag = EditTunnelContainerFragment.newInstance(tunnelId);
getChildFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, editFrag)
.addToBackStack("")
.commit();
}
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
// Should only get here in two-pane mode, but just to be safe:
if (mTwoPane) {
if (numTunnelsLeft > 0) {
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(
(tunnelId > 0 ? tunnelId - 1 : 0));
getChildFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, detailFrag).commit();
} else {
TunnelDetailFragment detailFrag = (TunnelDetailFragment) getChildFragmentManager().findFragmentById(R.id.detail_fragment);
getChildFragmentManager().beginTransaction()
.remove(detailFrag).commit();
}
}
}
}

View File

@ -0,0 +1,193 @@
package net.i2p.android.i2ptunnel.preferences;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceScreen;
import net.i2p.android.i2ptunnel.util.TunnelLogic;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
public class AdvancedTunnelPreferenceFragment extends BaseTunnelPreferenceFragment {
public static AdvancedTunnelPreferenceFragment newInstance(int tunnelId) {
AdvancedTunnelPreferenceFragment f = new AdvancedTunnelPreferenceFragment();
Bundle args = new Bundle();
args.putInt(ARG_TUNNEL_ID, tunnelId);
f.setArguments(args);
return f;
}
@Override
protected void loadPreferences() {
String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
new TunnelPreferences(type).runLogic();
}
class TunnelPreferences extends TunnelLogic {
PreferenceScreen ps;
PreferenceCategory tunParamCategory;
public TunnelPreferences(String type) {
super(type);
}
@Override
protected void general() {
}
@Override
protected void generalClient() {
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
}
@Override
protected void generalClientPort() {
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
}
@Override
protected void generalClientProxy(boolean isProxy) {
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
}
@Override
protected void generalClientIrc() {
}
@Override
protected void generalServerHttp() {
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
}
@Override
protected void generalServerPort() {
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
}
@Override
protected void advanced() {
addPreferencesFromResource(R.xml.tunnel_adv);
ps = getPreferenceScreen();
tunParamCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_TUNNEL_PARAMS));
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (isStreamr)
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_PROFILE)));
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (isServerOrStreamrClient)
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_DELAY_CONNECT)));
}
@Override
protected void advancedServer() {
addPreferencesFromResource(R.xml.tunnel_adv_server);
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp)
addPreferencesFromResource(R.xml.tunnel_adv_server_http);
else {
PreferenceCategory accessCtlCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_ACCESS_CONTROL));
accessCtlCategory.removePreference(accessCtlCategory.findPreference(getString(R.string.TUNNEL_OPT_REJECT_INPROXY)));
}
}
@Override
protected void advancedIdle() {
addPreferencesFromResource(R.xml.tunnel_adv_idle);
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (isServerOrStreamrClient)
ps.removePreference(ps.findPreference(getString(R.string.TUNNEL_OPT_DELAY_OPEN)));
}
@Override
protected void advancedClient() {
PreferenceCategory idleCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_IDLE)
);
addPreferencesFromResource(R.xml.tunnel_adv_idle_client, idleCategory);
// PERSISTENT_KEY and NEW_KEYS can't be set simultaneously
final CheckBoxPreference nk = (CheckBoxPreference) findPreference(getString(R.string.TUNNEL_OTP_NEW_KEYS));
nk.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
if ((Boolean) o && prefs.getBoolean(getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
getResources().getBoolean(R.bool.DEFAULT_PERSISTENT_KEY))) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.new_keys_on_reopen_conflict_title)
.setMessage(R.string.new_keys_on_reopen_conflict_msg)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(getString(R.string.TUNNEL_OPT_PERSISTENT_KEY), false);
editor.apply();
nk.setChecked(true);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
return false;
} else
return true;
}
});
}
@Override
protected void advancedClientHttp() {
addPreferencesFromResource(R.xml.tunnel_adv_client_http);
}
@Override
protected void advancedClientProxy() {
addPreferencesFromResource(R.xml.tunnel_adv_client_proxy);
}
@Override
protected void advancedOther() {
addPreferencesFromResource(R.xml.tunnel_adv_other);
}
}
}

View File

@ -0,0 +1,87 @@
package net.i2p.android.i2ptunnel.preferences;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.preferences.util.CustomPreferenceFragment;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
protected static final String ARG_TUNNEL_ID = "tunnelId";
protected TunnelControllerGroup mGroup;
protected int mTunnelId;
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
// TODO Show error
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
loadPreferences();
}
}
@Override
public void onPause() {
super.onPause();
// Pre-Honeycomb: onPause() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
@Override
public void onStop() {
super.onStop();
// Honeycomb and above: onStop() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
private void saveTunnel() {
if (mGroup != null) {
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
}
}
protected abstract void loadPreferences();
/**
* http://stackoverflow.com/a/20806812
*
* @param id the Preferences XML to load
* @param newParent the parent PreferenceGroup to add the new Preferences to.
*/
protected void addPreferencesFromResource(int id, PreferenceGroup newParent) {
PreferenceScreen screen = getPreferenceScreen();
int last = screen.getPreferenceCount();
addPreferencesFromResource(id);
while (screen.getPreferenceCount() > last) {
Preference p = screen.getPreference(last);
screen.removePreference(p); // decreases the preference count
newParent.addPreference(p);
}
}
}

View File

@ -0,0 +1,59 @@
package net.i2p.android.i2ptunnel.preferences;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import net.i2p.android.i2ptunnel.TunnelDetailActivity;
import net.i2p.android.i2ptunnel.TunnelDetailFragment;
import net.i2p.android.router.R;
import net.i2p.android.util.LocaleManager;
public class EditTunnelActivity extends AppCompatActivity {
private int mTunnelId;
private final LocaleManager localeManager = new LocaleManager();
@Override
public void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
mTunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
Fragment editFrag = GeneralTunnelPreferenceFragment.newInstance(mTunnelId);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment, editFrag).commit();
}
}
@Override
public void onResume() {
super.onResume();
localeManager.onResume(this);
}
@Override
public boolean onSupportNavigateUp() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else {
Intent intent = new Intent(this, TunnelDetailActivity.class);
intent.putExtra(TunnelDetailFragment.TUNNEL_ID, mTunnelId);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
return true;
}
}

View File

@ -0,0 +1,64 @@
package net.i2p.android.i2ptunnel.preferences;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import net.i2p.android.router.R;
/**
* A shim that emulates EditTunnelActivity to provide a Toolbar with navigation
* in two-pane mode.
*/
public class EditTunnelContainerFragment extends Fragment {
private static final String ARG_TUNNEL_ID = "tunnelId";
public static EditTunnelContainerFragment newInstance(int tunnelId) {
EditTunnelContainerFragment f = new EditTunnelContainerFragment();
Bundle args = new Bundle();
args.putInt(ARG_TUNNEL_ID, tunnelId);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.activity_single_fragment, container, false);
// Set the action bar
Toolbar toolbar = (Toolbar) v.findViewById(R.id.main_toolbar);
toolbar.setTitle(R.string.edit_tunnel);
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Try and navigate back through the edit tunnel fragments.
// Otherwise, pop us back off.
FragmentManager fragmentManager = getChildFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0)
fragmentManager.popBackStack();
else
getFragmentManager().popBackStack();
}
});
return v;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null) {
int tunnelId = getArguments().getInt(ARG_TUNNEL_ID);
BaseTunnelPreferenceFragment editFrag = GeneralTunnelPreferenceFragment.newInstance(tunnelId);
getChildFragmentManager().beginTransaction()
.add(R.id.fragment, editFrag).commit();
}
}
}

View File

@ -0,0 +1,263 @@
package net.i2p.android.i2ptunnel.preferences;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceScreen;
import net.i2p.android.i2ptunnel.util.TunnelLogic;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.util.Addresses;
import java.util.Set;
public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragment {
private CheckBoxPreference persistentKeys;
public static GeneralTunnelPreferenceFragment newInstance(int tunnelId) {
GeneralTunnelPreferenceFragment f = new GeneralTunnelPreferenceFragment();
Bundle args = new Bundle();
args.putInt(ARG_TUNNEL_ID, tunnelId);
f.setArguments(args);
return f;
}
@Override
protected void loadPreferences() {
String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
new TunnelPreferences(type).runLogic();
}
@Override
public void onStart() {
super.onStart();
// In case this was changed when toggling NEW_KEYS and then we navigated back
if (persistentKeys != null)
persistentKeys.setChecked(getPreferenceManager().getSharedPreferences().getBoolean(
getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
getResources().getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)
));
}
class TunnelPreferences extends TunnelLogic {
PreferenceScreen ps;
PreferenceCategory generalCategory;
PreferenceCategory portCategory;
public TunnelPreferences(String type) {
super(type);
}
@Override
protected void general() {
addPreferencesFromResource(R.xml.tunnel_gen);
ps = getPreferenceScreen();
generalCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_GENERAL));
portCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_PORT));
}
@Override
protected void generalClient() {
addPreferencesFromResource(R.xml.tunnel_gen_client, generalCategory);
// PERSISTENT_KEY and NEW_KEYS can't be set simultaneously
persistentKeys = (CheckBoxPreference) findPreference(getString(R.string.TUNNEL_OPT_PERSISTENT_KEY));
persistentKeys.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
if ((Boolean) o && prefs.getBoolean(getString(R.string.TUNNEL_OTP_NEW_KEYS),
getResources().getBoolean(R.bool.DEFAULT_NEW_KEYS))) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.persistent_key_conflict_title)
.setMessage(R.string.persistent_key_conflict_msg)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(getString(R.string.TUNNEL_OTP_NEW_KEYS), false);
editor.apply();
persistentKeys.setChecked(true);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
return false;
} else
return true;
}
});
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr) {
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_SHARED_CLIENT)));
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_PORT)));
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
}
@Override
protected void generalClientPort() {
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
ListPreference reachableBy = (ListPreference) portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE));
if (isStreamr)
portCategory.removePreference(reachableBy);
else
setupReachableBy(reachableBy);
}
private void setupReachableBy(final ListPreference reachableBy) {
reachableBy.setEnabled(false);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
Set<String> interfaceSet = Addresses.getAllAddresses();
final String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
reachableBy.setEntries(interfaces);
reachableBy.setEntryValues(interfaces);
reachableBy.setEnabled(true);
}
});
return null;
}
}.execute();
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy) {
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_DEST)));
addPreferencesFromResource(R.xml.tunnel_gen_client_proxy);
}
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (!isHttp)
ps.removePreference(ps.findPreference(getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES)));
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
if (!isStandardOrIrc)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
@Override
protected void generalClientIrc() {
addPreferencesFromResource(R.xml.tunnel_gen_client_irc);
}
@Override
protected void generalServerHttp() {
addPreferencesFromResource(R.xml.tunnel_gen_server_http, generalCategory);
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
if (isStreamr)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT)));
setupReachableBy((ListPreference) portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE)));
}
@Override
protected void generalServerPort() {
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (isStreamr) {
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_HOST)));
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
}
@Override
protected void advanced() {
Preference advanced = new Preference(getActivity());
advanced.setKey(getString(R.string.TUNNEL_CAT_ADVANCED));
advanced.setTitle(R.string.settings_label_advanced);
advanced.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Fragment fragment = AdvancedTunnelPreferenceFragment.newInstance(mTunnelId);
getFragmentManager().beginTransaction()
.replace(R.id.fragment, fragment)
.addToBackStack(null)
.commit();
return true;
}
});
ps.addPreference(advanced);
}
@Override
protected void advancedStreamr(boolean isStreamr) {
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
}
@Override
protected void advancedServer() {
}
@Override
protected void advancedServerHttp(boolean isHttp) {
}
@Override
protected void advancedIdle() {
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
}
@Override
protected void advancedClient() {
}
@Override
protected void advancedClientHttp() {
}
@Override
protected void advancedClientProxy() {
}
@Override
protected void advancedOther() {
}
}
}

View File

@ -1,668 +0,0 @@
package net.i2p.android.i2ptunnel.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.Page;
import net.i2p.i2ptunnel.I2PTunnelConnectClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClient;
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.PasswordManager;
public class TunnelConfig {
protected final I2PAppContext _context;
private String _type;
private String _name;
private String _description;
private String _i2cpHost;
private String _i2cpPort;
private String _tunnelDepth;
private String _tunnelQuantity;
private String _tunnelVariance;
private String _tunnelBackupQuantity;
private boolean _connectDelay;
private String _customOptions;
private String _proxyList;
private String _port;
private String _reachableBy;
private String _targetDestination;
private String _targetHost;
private String _targetPort;
private String _spoofedHost;
private String _privKeyFile;
private String _profile;
private boolean _startOnLoad;
private boolean _sharedClient;
private final Set<String> _booleanOptions;
private final Map<String, String> _otherOptions;
private String _newProxyUser;
private String _newProxyPW;
static final String CLIENT_NICKNAME = "shared clients";
public static TunnelConfig createFromWizard(
Context ctx, TunnelControllerGroup tcg, Bundle data) {
// Get the Bundle keys
Resources res = ctx.getResources();
String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
String kType = res.getString(R.string.i2ptunnel_wizard_k_type);
String kName = res.getString(R.string.i2ptunnel_wizard_k_name);
String kDesc = res.getString(R.string.i2ptunnel_wizard_k_desc);
String kDest = res.getString(R.string.i2ptunnel_wizard_k_dest);
String kOutproxies = res.getString(R.string.i2ptunnel_wizard_k_outproxies);
String kTargetHost = res.getString(R.string.i2ptunnel_wizard_k_target_host);
String kTargetPort = res.getString(R.string.i2ptunnel_wizard_k_target_port);
String kReachableOn = res.getString(R.string.i2ptunnel_wizard_k_reachable_on);
String kBindingPort = res.getString(R.string.i2ptunnel_wizard_k_binding_port);
String kAutoStart = res.getString(R.string.i2ptunnel_wizard_k_auto_start);
// Create the TunnelConfig
TunnelConfig cfg = new TunnelConfig();
// Get/set the tunnel wizard settings
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
String type = TunnelUtil.getTypeFromName(typeName, ctx);
cfg.setType(type);
String name = data.getBundle(kName).getString(Page.SIMPLE_DATA_KEY);
cfg.setName(name);
String desc = data.getBundle(kDesc).getString(Page.SIMPLE_DATA_KEY);
cfg.setDescription(desc);
String dest = null;
Bundle pageData = data.getBundle(kDest);
if (pageData != null) dest = pageData.getString(Page.SIMPLE_DATA_KEY);
cfg.setTargetDestination(dest);
String outproxies = null;
pageData = data.getBundle(kOutproxies);
if (pageData != null) outproxies = pageData.getString(Page.SIMPLE_DATA_KEY);
cfg.setProxyList(outproxies);
String targetHost = null;
pageData = data.getBundle(kTargetHost);
if (pageData != null) targetHost = pageData.getString(Page.SIMPLE_DATA_KEY);
cfg.setTargetHost(targetHost);
String targetPort = null;
pageData = data.getBundle(kTargetPort);
if (pageData != null) targetPort = pageData.getString(Page.SIMPLE_DATA_KEY);
cfg.setTargetPort(targetPort);
String reachableOn = null;
pageData = data.getBundle(kReachableOn);
if (pageData != null) reachableOn = pageData.getString(Page.SIMPLE_DATA_KEY);
cfg.setReachableBy(reachableOn);
String bindingPort = null;
pageData = data.getBundle(kBindingPort);
if (pageData != null) bindingPort = pageData.getString(Page.SIMPLE_DATA_KEY);
cfg.setPort(bindingPort);
boolean autoStart = data.getBundle(kAutoStart).getBoolean(Page.SIMPLE_DATA_KEY);
cfg.setStartOnLoad(autoStart);
// Set sensible defaults for a new tunnel
cfg.setTunnelDepth("3");
cfg.setTunnelVariance("0");
cfg.setTunnelQuantity("2");
cfg.setTunnelBackupQuantity("0");
cfg.setClientHost("internal");
cfg.setClientport("internal");
cfg.setCustomOptions("");
if (!"streamrclient".equals(type)) {
cfg.setProfile("bulk");
cfg.setReduceCount("1");
cfg.setReduceTime("20");
}
if (TunnelUtil.isClient(type)) { /* Client-only defaults */
if (!"streamrclient".equals(type)) {
cfg.setNewDest("0");
cfg.setCloseTime("30");
}
if ("httpclient".equals(type) ||
"connectclient".equals(type) ||
"sockstunnel".equals(type) |
"socksirctunnel".equals(type)) {
cfg.setProxyUsername("");
cfg.setProxyPassword("");
cfg.setOutproxyUsername("");
cfg.setOutproxyPassword("");
}
if ("httpclient".equals(type))
cfg.setJumpList("http://i2host.i2p/cgi-bin/i2hostjump?\nhttp://stats.i2p/cgi-bin/jump.cgi?a=");
} else { /* Server-only defaults */
cfg.setPrivKeyFile(TunnelUtil.getPrivateKeyFile(tcg, -1));
cfg.setEncrypt("");
cfg.setEncryptKey("");
cfg.setAccessMode("0");
cfg.setAccessList("");
cfg.setLimitMinute("0");
cfg.setLimitHour("0");
cfg.setLimitDay("0");
cfg.setTotalMinute("0");
cfg.setTotalHour("0");
cfg.setTotalDay("0");
cfg.setMaxStreams("0");
}
return cfg;
}
public TunnelConfig() {
_context = I2PAppContext.getGlobalContext();
_booleanOptions = new ConcurrentHashSet<String>(4);
_otherOptions = new ConcurrentHashMap<String,String>(4);
}
/**
* What type of tunnel (httpclient, ircclient, client, or server). This is
* required when adding a new tunnel.
*
*/
public void setType(String type) {
_type = (type != null ? type.trim() : null);
}
String getType() { return _type; }
/** Short name of the tunnel */
public void setName(String name) {
_name = (name != null ? name.trim() : null);
}
/** one line description */
public void setDescription(String description) {
_description = (description != null ? description.trim() : null);
}
/** I2CP host the router is on, ignored when in router context */
public void setClientHost(String host) {
_i2cpHost = (host != null ? host.trim() : null);
}
/** I2CP port the router is on, ignored when in router context */
public void setClientport(String port) {
_i2cpPort = (port != null ? port.trim() : null);
}
/** how many hops to use for inbound tunnels */
public void setTunnelDepth(String tunnelDepth) {
_tunnelDepth = (tunnelDepth != null ? tunnelDepth.trim() : null);
}
/** how many parallel inbound tunnels to use */
public void setTunnelQuantity(String tunnelQuantity) {
_tunnelQuantity = (tunnelQuantity != null ? tunnelQuantity.trim() : null);
}
/** how much randomisation to apply to the depth of tunnels */
public void setTunnelVariance(String tunnelVariance) {
_tunnelVariance = (tunnelVariance != null ? tunnelVariance.trim() : null);
}
/** how many tunnels to hold in reserve to guard against failures */
public void setTunnelBackupQuantity(String tunnelBackupQuantity) {
_tunnelBackupQuantity = (tunnelBackupQuantity != null ? tunnelBackupQuantity.trim() : null);
}
/** what I2P session overrides should be used */
public void setCustomOptions(String customOptions) {
_customOptions = (customOptions != null ? customOptions.trim() : null);
}
/** what HTTP outproxies should be used (httpclient specific) */
public void setProxyList(String proxyList) {
_proxyList = (proxyList != null ? proxyList.trim() : null);
}
/** what port should this client/httpclient/ircclient listen on */
public void setPort(String port) {
_port = (port != null ? port.trim() : null);
}
/**
* what interface should this client/httpclient/ircclient listen on
*/
public void setReachableBy(String reachableBy) {
_reachableBy = (reachableBy != null ? reachableBy.trim() : null);
}
/** What peer does this client tunnel point at */
public void setTargetDestination(String dest) {
_targetDestination = (dest != null ? dest.trim() : null);
}
/** What host does this server tunnel point at */
public void setTargetHost(String host) {
_targetHost = (host != null ? host.trim() : null);
}
/** What port does this server tunnel point at */
public void setTargetPort(String port) {
_targetPort = (port != null ? port.trim() : null);
}
/** What host does this http server tunnel spoof */
public void setSpoofedHost(String host) {
_spoofedHost = (host != null ? host.trim() : null);
}
/** What filename is this server tunnel's private keys stored in */
public void setPrivKeyFile(String file) {
_privKeyFile = (file != null ? file.trim() : null);
}
/**
* If called with true, we want this tunnel to start whenever it is
* loaded (aka right now and whenever the router is started up)
*/
public void setStartOnLoad(boolean val) {
_startOnLoad = val;
}
public void setShared(boolean val) {
_sharedClient=val;
}
public void setConnectDelay(String moo) {
_connectDelay = true;
}
public void setProfile(String profile) {
_profile = profile;
}
public void setReduce(String moo) {
_booleanOptions.add("i2cp.reduceOnIdle");
}
public void setClose(String moo) {
_booleanOptions.add("i2cp.closeOnIdle");
}
public void setEncrypt(String moo) {
_booleanOptions.add("i2cp.encryptLeaseSet");
}
/** @since 0.8.9 */
public void setDCC(String moo) {
_booleanOptions.add(I2PTunnelIRCClient.PROP_DCC);
}
protected static final String PROP_ENABLE_ACCESS_LIST = "i2cp.enableAccessList";
protected static final String PROP_ENABLE_BLACKLIST = "i2cp.enableBlackList";
public void setAccessMode(String val) {
if ("1".equals(val))
_booleanOptions.add(PROP_ENABLE_ACCESS_LIST);
else if ("2".equals(val))
_booleanOptions.add(PROP_ENABLE_BLACKLIST);
}
public void setDelayOpen(String moo) {
_booleanOptions.add("i2cp.delayOpen");
}
public void setNewDest(String val) {
if ("1".equals(val))
_booleanOptions.add("i2cp.newDestOnResume");
else if ("2".equals(val))
_booleanOptions.add("persistentClientKey");
}
public void setReduceTime(String val) {
if (val != null) {
try {
_otherOptions.put("i2cp.reduceIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
} catch (NumberFormatException nfe) {}
}
}
public void setReduceCount(String val) {
if (val != null)
_otherOptions.put("i2cp.reduceQuantity", val.trim());
}
public void setEncryptKey(String val) {
if (val != null)
_otherOptions.put("i2cp.leaseSetKey", val.trim());
}
public void setAccessList(String val) {
if (val != null)
_otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
}
public void setJumpList(String val) {
if (val != null)
_otherOptions.put(I2PTunnelHTTPClient.PROP_JUMP_SERVERS, val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
}
public void setCloseTime(String val) {
if (val != null) {
try {
_otherOptions.put("i2cp.closeIdleTime", "" + (Integer.parseInt(val.trim()) * 60*1000));
} catch (NumberFormatException nfe) {}
}
}
/** all proxy auth @since 0.8.2 */
public void setProxyAuth(String s) {
if (s != null)
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
}
public void setProxyUsername(String s) {
if (s != null)
_newProxyUser = s.trim();
}
public void setProxyPassword(String s) {
if (s != null)
_newProxyPW = s.trim();
}
public void setOutproxyAuth(String s) {
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH, I2PTunnelHTTPClientBase.DIGEST_AUTH);
}
public void setOutproxyUsername(String s) {
if (s != null)
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER, s.trim());
}
public void setOutproxyPassword(String s) {
if (s != null)
_otherOptions.put(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW, s.trim());
}
/** all of these are @since 0.8.3 */
protected static final String PROP_MAX_CONNS_MIN = "i2p.streaming.maxConnsPerMinute";
protected static final String PROP_MAX_CONNS_HOUR = "i2p.streaming.maxConnsPerHour";
protected static final String PROP_MAX_CONNS_DAY = "i2p.streaming.maxConnsPerDay";
protected static final String PROP_MAX_TOTAL_CONNS_MIN = "i2p.streaming.maxTotalConnsPerMinute";
protected static final String PROP_MAX_TOTAL_CONNS_HOUR = "i2p.streaming.maxTotalConnsPerHour";
protected static final String PROP_MAX_TOTAL_CONNS_DAY = "i2p.streaming.maxTotalConnsPerDay";
protected static final String PROP_MAX_STREAMS = "i2p.streaming.maxConcurrentStreams";
public void setLimitMinute(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_CONNS_MIN, s.trim());
}
public void setLimitHour(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_CONNS_HOUR, s.trim());
}
public void setLimitDay(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_CONNS_DAY, s.trim());
}
public void setTotalMinute(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_TOTAL_CONNS_MIN, s.trim());
}
public void setTotalHour(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_TOTAL_CONNS_HOUR, s.trim());
}
public void setTotalDay(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_TOTAL_CONNS_DAY, s.trim());
}
public void setMaxStreams(String s) {
if (s != null)
_otherOptions.put(PROP_MAX_STREAMS, s.trim());
}
/**
* Based on all provided data, create a set of configuration parameters
* suitable for use in a TunnelController. This will replace (not add to)
* any existing parameters, so this should return a comprehensive mapping.
*
*/
public Properties getConfig() {
Properties config = new Properties();
updateConfigGeneric(config);
if ((TunnelUtil.isClient(_type) && !"streamrclient".equals(_type)) || "streamrserver".equals(_type)) {
// streamrserver uses interface
if (_reachableBy != null)
config.setProperty("interface", _reachableBy);
else
config.setProperty("interface", "");
} else {
// streamrclient uses targetHost
if (_targetHost != null)
config.setProperty("targetHost", _targetHost);
}
if (TunnelUtil.isClient(_type)) {
// generic client stuff
if (_port != null)
config.setProperty("listenPort", _port);
config.setProperty("sharedClient", _sharedClient + "");
for (String p : _booleanClientOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
for (String p : _otherClientOpts)
if (_otherOptions.containsKey(p))
config.setProperty("option." + p, _otherOptions.get(p));
} else {
// generic server stuff
if (_targetPort != null)
config.setProperty("targetPort", _targetPort);
for (String p : _booleanServerOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
for (String p : _otherServerOpts)
if (_otherOptions.containsKey(p))
config.setProperty("option." + p, _otherOptions.get(p));
}
// generic proxy stuff
if ("httpclient".equals(_type) || "connectclient".equals(_type) ||
"sockstunnel".equals(_type) ||"socksirctunnel".equals(_type)) {
for (String p : _booleanProxyOpts)
config.setProperty("option." + p, "" + _booleanOptions.contains(p));
if (_proxyList != null)
config.setProperty("proxyList", _proxyList);
}
// Proxy auth including migration to MD5
if ("httpclient".equals(_type) || "connectclient".equals(_type)) {
// Migrate even if auth is disabled
// go get the old from custom options that updateConfigGeneric() put in there
String puser = "option." + I2PTunnelHTTPClientBase.PROP_USER;
String user = config.getProperty(puser);
String ppw = "option." + I2PTunnelHTTPClientBase.PROP_PW;
String pw = config.getProperty(ppw);
if (user != null && pw != null && user.length() > 0 && pw.length() > 0) {
String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
user + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
if (config.getProperty(pmd5) == null) {
// not in there, migrate
String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
: I2PTunnelConnectClient.AUTH_REALM;
String hex = PasswordManager.md5Hex(realm, user, pw);
if (hex != null) {
config.setProperty(pmd5, hex);
config.remove(puser);
config.remove(ppw);
}
}
}
// New user/password
String auth = _otherOptions.get(I2PTunnelHTTPClientBase.PROP_AUTH);
if (auth != null && !auth.equals("false")) {
if (_newProxyUser != null && _newProxyPW != null &&
_newProxyUser.length() > 0 && _newProxyPW.length() > 0) {
String pmd5 = "option." + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_PREFIX +
_newProxyUser + I2PTunnelHTTPClientBase.PROP_PROXY_DIGEST_SUFFIX;
String realm = _type.equals("httpclient") ? I2PTunnelHTTPClient.AUTH_REALM
: I2PTunnelConnectClient.AUTH_REALM;
String hex = PasswordManager.md5Hex(realm, _newProxyUser, _newProxyPW);
if (hex != null)
config.setProperty(pmd5, hex);
}
}
}
if ("ircclient".equals(_type) || "client".equals(_type) || "streamrclient".equals(_type)) {
if (_targetDestination != null)
config.setProperty("targetDestination", _targetDestination);
} else if ("httpserver".equals(_type) || "httpbidirserver".equals(_type)) {
if (_spoofedHost != null)
config.setProperty("spoofedHost", _spoofedHost);
}
if ("httpbidirserver".equals(_type)) {
if (_port != null)
config.setProperty("listenPort", _port);
if (_reachableBy != null)
config.setProperty("interface", _reachableBy);
else if (_targetHost != null)
config.setProperty("interface", _targetHost);
else
config.setProperty("interface", "");
}
if ("ircclient".equals(_type)) {
boolean dcc = _booleanOptions.contains(I2PTunnelIRCClient.PROP_DCC);
config.setProperty("option." + I2PTunnelIRCClient.PROP_DCC,
"" + dcc);
// add some sane server options since they aren't in the GUI (yet)
if (dcc) {
config.setProperty("option." + PROP_MAX_CONNS_MIN, "3");
config.setProperty("option." + PROP_MAX_CONNS_HOUR, "10");
config.setProperty("option." + PROP_MAX_TOTAL_CONNS_MIN, "5");
config.setProperty("option." + PROP_MAX_TOTAL_CONNS_HOUR, "25");
}
}
return config;
}
private static final String _noShowOpts[] = {
"inbound.length", "outbound.length", "inbound.lengthVariance", "outbound.lengthVariance",
"inbound.backupQuantity", "outbound.backupQuantity", "inbound.quantity", "outbound.quantity",
"inbound.nickname", "outbound.nickname", "i2p.streaming.connectDelay", "i2p.streaming.maxWindowSize",
I2PTunnelIRCClient.PROP_DCC
};
private static final String _booleanClientOpts[] = {
"i2cp.reduceOnIdle", "i2cp.closeOnIdle", "i2cp.newDestOnResume", "persistentClientKey", "i2cp.delayOpen"
};
private static final String _booleanProxyOpts[] = {
I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH
};
private static final String _booleanServerOpts[] = {
"i2cp.reduceOnIdle", "i2cp.encryptLeaseSet", PROP_ENABLE_ACCESS_LIST, PROP_ENABLE_BLACKLIST
};
private static final String _otherClientOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.closeIdleTime",
"outproxyUsername", "outproxyPassword",
I2PTunnelHTTPClient.PROP_JUMP_SERVERS,
I2PTunnelHTTPClientBase.PROP_AUTH
};
private static final String _otherServerOpts[] = {
"i2cp.reduceIdleTime", "i2cp.reduceQuantity", "i2cp.leaseSetKey", "i2cp.accessList",
PROP_MAX_CONNS_MIN, PROP_MAX_CONNS_HOUR, PROP_MAX_CONNS_DAY,
PROP_MAX_TOTAL_CONNS_MIN, PROP_MAX_TOTAL_CONNS_HOUR, PROP_MAX_TOTAL_CONNS_DAY,
PROP_MAX_STREAMS
};
/**
* do NOT add these to noShoOpts, we must leave them in for HTTPClient and ConnectCLient
* so they will get migrated to MD5
* TODO migrate socks to MD5
*/
private static final String _otherProxyOpts[] = {
"proxyUsername", "proxyPassword"
};
protected static final Set<String> _noShowSet = new HashSet<String>(64);
protected static final Set<String> _nonProxyNoShowSet = new HashSet<String>(4);
static {
_noShowSet.addAll(Arrays.asList(_noShowOpts));
_noShowSet.addAll(Arrays.asList(_booleanClientOpts));
_noShowSet.addAll(Arrays.asList(_booleanProxyOpts));
_noShowSet.addAll(Arrays.asList(_booleanServerOpts));
_noShowSet.addAll(Arrays.asList(_otherClientOpts));
_noShowSet.addAll(Arrays.asList(_otherServerOpts));
_nonProxyNoShowSet.addAll(Arrays.asList(_otherProxyOpts));
}
private void updateConfigGeneric(Properties config) {
config.setProperty("type", _type);
if (_name != null)
config.setProperty("name", _name);
if (_description != null)
config.setProperty("description", _description);
if (!_context.isRouterContext()) {
if (_i2cpHost != null)
config.setProperty("i2cpHost", _i2cpHost);
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) {
config.setProperty("i2cpPort", _i2cpPort);
} else {
config.setProperty("i2cpPort", "7654");
}
}
if (_privKeyFile != null)
config.setProperty("privKeyFile", _privKeyFile);
if (_customOptions != null) {
StringTokenizer tok = new StringTokenizer(_customOptions);
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
if (_noShowSet.contains(key))
continue;
// leave in for HTTP and Connect so it can get migrated to MD5
// hide for SOCKS until migrated to MD5
if ((!"httpclient".equals(_type)) &&
(! "connectclient".equals(_type)) &&
_nonProxyNoShowSet.contains(key))
continue;
String val = pair.substring(eq+1);
config.setProperty("option." + key, val);
}
}
config.setProperty("startOnLoad", _startOnLoad + "");
if (_tunnelQuantity != null) {
config.setProperty("option.inbound.quantity", _tunnelQuantity);
config.setProperty("option.outbound.quantity", _tunnelQuantity);
}
if (_tunnelDepth != null) {
config.setProperty("option.inbound.length", _tunnelDepth);
config.setProperty("option.outbound.length", _tunnelDepth);
}
if (_tunnelVariance != null) {
config.setProperty("option.inbound.lengthVariance", _tunnelVariance);
config.setProperty("option.outbound.lengthVariance", _tunnelVariance);
}
if (_tunnelBackupQuantity != null) {
config.setProperty("option.inbound.backupQuantity", _tunnelBackupQuantity);
config.setProperty("option.outbound.backupQuantity", _tunnelBackupQuantity);
}
if (_connectDelay)
config.setProperty("option.i2p.streaming.connectDelay", "1000");
else
config.setProperty("option.i2p.streaming.connectDelay", "0");
if (TunnelUtil.isClient(_type) && _sharedClient) {
config.setProperty("option.inbound.nickname", CLIENT_NICKNAME);
config.setProperty("option.outbound.nickname", CLIENT_NICKNAME);
} else if (_name != null) {
config.setProperty("option.inbound.nickname", _name);
config.setProperty("option.outbound.nickname", _name);
}
if ("interactive".equals(_profile))
// This was 1 which doesn't make much sense
// The real way to make it interactive is to make the streaming lib
// MessageInputStream flush faster but there's no option for that yet,
// Setting it to 16 instead of the default but not sure what good that is either.
config.setProperty("option.i2p.streaming.maxWindowSize", "16");
else
config.remove("option.i2p.streaming.maxWindowSize");
}
}

View File

@ -0,0 +1,101 @@
package net.i2p.android.i2ptunnel.util;
/**
* Generic class for handling the composition of tunnel properties.
* <p/>
* See I2PTunnel's editClient.jsp and editServer.jsp for composition logic.
* <p/>
* Some of the abstract methods have boolean parameters. These are the methods
* where the corresponding tunnel properties may or may not exist, depending on
* the value of the boolean. In all other abstract methods, all corresponding
* tunnel properties always exist.
*/
public abstract class TunnelLogic {
protected String mType;
public TunnelLogic(String type) {
mType = type;
}
public void runLogic() {
boolean isProxy = "httpclient".equals(mType) ||
"connectclient".equals(mType) ||
"sockstunnel".equals(mType) ||
"socksirctunnel".equals(mType);
general();
if (TunnelUtil.isClient(mType)) {
generalClient();
generalClientStreamr("streamrclient".equals(mType));
generalClientPort();
generalClientPortStreamr("streamrclient".equals(mType));
generalClientProxy(isProxy);
if (isProxy)
generalClientProxyHttp("httpclient".equals(mType));
generalClientStandardOrIrc("client".equals(mType) || "ircclient".equals(mType));
if ("ircclient".equals(mType))
generalClientIrc();
} else {
if ("httpserver".equals(mType) || "httpbidirserver".equals(mType))
generalServerHttp();
if ("httpbidirserver".equals(mType) || "streamrserver".equals(mType))
generalServerHttpBidirOrStreamr("streamrserver".equals(mType));
generalServerPort();
generalServerPortStreamr("streamrserver".equals(mType));
}
advanced();
advancedStreamr("streamrclient".equals(mType) || "streamrserver".equals(mType));
advancedServerOrStreamrClient(!TunnelUtil.isClient(mType) || "streamrclient".equals(mType));
if (!TunnelUtil.isClient(mType)) {
advancedServer();
advancedServerHttp("httpserver".equals(mType) || "httpbidirserver".equals(mType));
}
advancedIdle();
// streamr client sends pings so it will never be idle
advancedIdleServerOrStreamrClient(!TunnelUtil.isClient(mType) || "streamrclient".equals(mType));
if (TunnelUtil.isClient(mType)) {
advancedClient();
if ("httpclient".equals(mType))
advancedClientHttp();
if (isProxy)
advancedClientProxy();
}
advancedOther();
}
protected abstract void general();
protected abstract void generalClient();
protected abstract void generalClientStreamr(boolean isStreamr);
protected abstract void generalClientPort();
protected abstract void generalClientPortStreamr(boolean isStreamr);
protected abstract void generalClientProxy(boolean isProxy);
protected abstract void generalClientProxyHttp(boolean isHttp);
protected abstract void generalClientStandardOrIrc(boolean isStandardOrIrc);
protected abstract void generalClientIrc();
protected abstract void generalServerHttp();
protected abstract void generalServerHttpBidirOrStreamr(boolean isStreamr);
protected abstract void generalServerPort();
protected abstract void generalServerPortStreamr(boolean isStreamr);
protected abstract void advanced();
protected abstract void advancedStreamr(boolean isStreamr);
protected abstract void advancedServerOrStreamrClient(boolean isServerOrStreamrClient);
protected abstract void advancedServer();
protected abstract void advancedServerHttp(boolean isHttp);
protected abstract void advancedIdle();
protected abstract void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient);
protected abstract void advancedClient();
protected abstract void advancedClientHttp();
protected abstract void advancedClientProxy();
protected abstract void advancedOther();
}

View File

@ -1,161 +1,27 @@
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.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.android.wizard.model.Page;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.FileUtil;
import net.i2p.util.SecureFile;
import net.i2p.i2ptunnel.ui.GeneralHelper;
import net.i2p.i2ptunnel.ui.TunnelConfig;
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 class TunnelUtil extends GeneralHelper {
public static final String PREFERENCES_FILENAME_PREFIX = "tunnel.";
public TunnelUtil(I2PAppContext context, TunnelControllerGroup tcg) {
super(context, tcg);
}
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;
public TunnelUtil(TunnelControllerGroup tcg) {
super(tcg);
}
/* General tunnel data for any type */
@ -192,50 +58,725 @@ public abstract class TunnelUtil {
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;
switch (type) {
case "client":
return res.getString(R.string.i2ptunnel_type_client);
case "httpclient":
return res.getString(R.string.i2ptunnel_type_httpclient);
case "ircclient":
return res.getString(R.string.i2ptunnel_type_ircclient);
case "server":
return res.getString(R.string.i2ptunnel_type_server);
case "httpserver":
return res.getString(R.string.i2ptunnel_type_httpserver);
case "sockstunnel":
return res.getString(R.string.i2ptunnel_type_sockstunnel);
case "socksirctunnel":
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
case "connectclient":
return res.getString(R.string.i2ptunnel_type_connectclient);
case "ircserver":
return res.getString(R.string.i2ptunnel_type_ircserver);
case "streamrclient":
return res.getString(R.string.i2ptunnel_type_streamrclient);
case "streamrserver":
return res.getString(R.string.i2ptunnel_type_streamrserver);
case "httpbidirserver":
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
default:
return type;
}
}
public static boolean isClient(String type) {
return ( ("client".equals(type)) ||
("httpclient".equals(type)) ||
("sockstunnel".equals(type)) ||
("socksirctunnel".equals(type)) ||
("connectclient".equals(type)) ||
("streamrclient".equals(type)) ||
("ircclient".equals(type)));
return TunnelController.isClient(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";
public static String getPreferencesFilename(int tunnel) {
return PREFERENCES_FILENAME_PREFIX + tunnel;
}
public static void writeTunnelToPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
new TunnelUtil(tcg).writeTunnelToPreferences(ctx, tunnel);
}
public void writeTunnelToPreferences(Context ctx, int tunnel) {
Resources res = ctx.getResources();
if (getController(tunnel) == null)
throw new IllegalArgumentException("Cannot write non-existent tunnel to Preferences");
// Get the current preferences for this tunnel
SharedPreferences preferences = ctx.getSharedPreferences(
getPreferencesFilename(tunnel), Context.MODE_PRIVATE);
// Clear all previous values
SharedPreferences.Editor ed = preferences.edit().clear();
// Load the tunnel config into the preferences
String type = getTunnelType(tunnel);
ed.putString(res.getString(R.string.TUNNEL_TYPE), type);
new TunnelToPreferences(ed, res, tunnel, type).runLogic();
ed.apply();
}
class TunnelToPreferences extends TunnelLogic {
SharedPreferences.Editor ed;
Resources res;
int tunnel;
public TunnelToPreferences(SharedPreferences.Editor ed, Resources res, int tunnel, String type) {
super(type);
this.ed = ed;
this.res = res;
this.tunnel = tunnel;
}
@Override
protected void general() {
ed.putString(res.getString(R.string.TUNNEL_NAME), getTunnelName(tunnel));
ed.putString(res.getString(R.string.TUNNEL_DESCRIPTION), getTunnelDescription(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_START_ON_LOAD), shouldStartAutomatically(tunnel));
if (!isClient(mType) || getPersistentClientKey(tunnel))
ed.putString(res.getString(R.string.TUNNEL_PRIV_KEY_FILE), getPrivateKeyFile(tunnel));
}
@Override
protected void generalClient() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY), getPersistentClientKey(tunnel));
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr)
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
else
ed.putBoolean(res.getString(R.string.TUNNEL_SHARED_CLIENT), isSharedClient(tunnel));
}
@Override
protected void generalClientPort() {
ed.putInt(res.getString(R.string.TUNNEL_LISTEN_PORT), getClientPort(tunnel));
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (!isStreamr)
ed.putString(res.getString(R.string.TUNNEL_INTERFACE), getClientInterface(tunnel));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy)
ed.putString(res.getString(R.string.TUNNEL_PROXIES), getClientDestination(tunnel));
else
ed.putString(res.getString(R.string.TUNNEL_DEST), getClientDestination(tunnel));
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (isHttp)
ed.putString(res.getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES), getSslProxies(tunnel));
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
if (isStandardOrIrc)
ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
}
@Override
protected void generalClientIrc() {
ed.putBoolean(res.getString(R.string.TUNNEL_IRCCLIENT_ENABLE_DCC), getDCC(tunnel));
}
@Override
protected void generalServerHttp() {
ed.putString(res.getString(R.string.TUNNEL_SPOOFED_HOST), getSpoofedHost(tunnel));
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
ed.putString(res.getString(R.string.TUNNEL_INTERFACE), getClientInterface(tunnel));
if (!isStreamr)
ed.putInt(res.getString(R.string.TUNNEL_LISTEN_PORT), getClientPort(tunnel));
}
@Override
protected void generalServerPort() {
ed.putInt(res.getString(R.string.TUNNEL_TARGET_PORT), getTargetPort(tunnel));
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
}
}
@Override
protected void advanced() {
ed.putInt(res.getString(R.string.TUNNEL_OPT_LENGTH),
getTunnelDepth(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_VARIANCE),
getTunnelVariance(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
getTunnelBackupQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (!isStreamr)
ed.putString(res.getString(R.string.TUNNEL_OPT_PROFILE),
isInteractive(tunnel) ? "interactive" : "bulk");
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_CONNECT),
shouldDelayConnect(tunnel));
}
@Override
protected void advancedServer() {
//ed.putBoolean(res.getString(R.string.TUNNEL_OPT_ENCRYPT), getEncrypt(tunnel));
//ed.putString(res.getString(R.string.TUNNEL_OPT_ENCRYPT_KEY), getEncryptKey(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_ACCESS_MODE), getAccessMode(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_ACCESS_LIST), getAccessList(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_UNIQUE_LOCAL), getUniqueLocal(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_MULTIHOME), getMultihome(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_MINUTE), getLimitMinute(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_HOUR), getLimitHour(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_DAY), getLimitDay(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_MINUTE), getTotalMinute(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_HOUR), getTotalHour(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_DAY), getTotalDay(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_MAX_STREAMS), getMaxStreams(tunnel));
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp) {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_REJECT_INPROXY), getRejectInproxy(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_CHECK_TIME), getPostCheckTime(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_MAX), getPostMax(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_BAN_TIME), getPostBanTime(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_MAX), getPostTotalMax(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_BAN_TIME), getPostTotalBanTime(tunnel));
}
}
@Override
protected void advancedIdle() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_REDUCE_IDLE),
getReduceOnIdle(tunnel, res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_REDUCE_QUANTITY),
getReduceCount(tunnel, res.getInteger(R.integer.DEFAULT_REDUCE_COUNT)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_REDUCE_TIME),
getReduceTime(tunnel, res.getInteger(R.integer.DEFAULT_REDUCE_TIME)));
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_OPEN), getDelayOpen(tunnel));
}
@Override
protected void advancedClient() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_CLOSE_IDLE),
getCloseOnIdle(tunnel, res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_CLOSE_TIME),
getCloseTime(tunnel, res.getInteger(R.integer.DEFAULT_CLOSE_TIME)));
ed.putBoolean(res.getString(R.string.TUNNEL_OTP_NEW_KEYS), getNewDest(tunnel));
}
@Override
protected void advancedClientHttp() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_UA), getAllowUserAgent(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_REFERER), getAllowReferer(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_ACCEPT), getAllowAccept(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_ALLOW_SSL), getAllowInternalSSL(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_JUMP_LIST), getJumpList(tunnel));
}
@Override
protected void advancedClientProxy() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_LOCAL_AUTH), !"false".equals(getProxyAuth(tunnel)));
ed.putString(res.getString(R.string.TUNNEL_OPT_LOCAL_USERNAME), "");
ed.putString(res.getString(R.string.TUNNEL_OPT_LOCAL_PASSWORD), "");
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_OUTPROXY_AUTH), getOutproxyAuth(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_USERNAME), getOutproxyUsername(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_PASSWORD), getOutproxyPassword(tunnel));
}
@Override
protected void advancedOther() {
ed.putInt(res.getString(R.string.TUNNEL_OPT_SIGTYPE), getSigType(tunnel, mType));
ed.putString(res.getString(R.string.TUNNEL_OPT_CUSTOM_OPTIONS), getCustomOptionsString(tunnel));
}
}
public static TunnelConfig createConfigFromPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
return new TunnelUtil(tcg).createConfigFromPreferences(ctx, tunnel);
}
public TunnelConfig createConfigFromPreferences(Context ctx, int tunnel) {
Resources res = ctx.getResources();
// Get the current preferences for this tunnel
SharedPreferences prefs = ctx.getSharedPreferences(
getPreferencesFilename(tunnel), Context.MODE_PRIVATE);
// Create the TunnelConfig
TunnelConfig cfg = new TunnelConfig();
// Update the TunnelConfig from the preferences
cfg.setType(prefs.getString(res.getString(R.string.TUNNEL_TYPE), null));
String type = cfg.getType();
new TunnelConfigFromPreferences(cfg, prefs, res, _group, tunnel, type).runLogic();
return cfg;
}
class TunnelConfigFromPreferences extends TunnelLogic {
TunnelConfig cfg;
SharedPreferences prefs;
Resources res;
TunnelControllerGroup tcg;
int tunnel;
public TunnelConfigFromPreferences(TunnelConfig cfg, SharedPreferences prefs, Resources res,
TunnelControllerGroup tcg, int tunnel, String type) {
super(type);
this.cfg = cfg;
this.prefs = prefs;
this.res = res;
this.tcg = tcg;
this.tunnel = tunnel;
}
@Override
protected void general() {
cfg.setName(prefs.getString(res.getString(R.string.TUNNEL_NAME), null));
cfg.setDescription(prefs.getString(res.getString(R.string.TUNNEL_DESCRIPTION), null));
cfg.setStartOnLoad(prefs.getBoolean(res.getString(R.string.TUNNEL_START_ON_LOAD),
res.getBoolean(R.bool.DEFAULT_START_ON_LOAD)));
if (!isClient(mType) || prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)))
cfg.setPrivKeyFile(prefs.getString(res.getString(R.string.TUNNEL_PRIV_KEY_FILE),
getPrivateKeyFile(tcg, tunnel)));
}
@Override
protected void generalClient() {
// See advancedClient() for persistent key handling
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr)
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), null));
else
cfg.setShared(prefs.getBoolean(res.getString(R.string.TUNNEL_SHARED_CLIENT),
res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS)));
}
@Override
protected void generalClientPort() {
cfg.setPort(prefs.getInt(res.getString(R.string.TUNNEL_LISTEN_PORT), -1));
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setReachableBy(prefs.getString(res.getString(R.string.TUNNEL_INTERFACE), "127.0.0.1"));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy)
cfg.setProxyList(prefs.getString(res.getString(R.string.TUNNEL_PROXIES), null));
else
cfg.setTargetDestination(prefs.getString(res.getString(R.string.TUNNEL_DEST), null));
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (isHttp)
cfg.setSslProxies(prefs.getString(res.getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES), null));
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
if (isStandardOrIrc)
cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
}
@Override
protected void generalClientIrc() {
cfg.setDCC(prefs.getBoolean(res.getString(R.string.TUNNEL_IRCCLIENT_ENABLE_DCC), false));
}
@Override
protected void generalServerHttp() {
cfg.setSpoofedHost(prefs.getString(res.getString(R.string.TUNNEL_SPOOFED_HOST), null));
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
cfg.setReachableBy(prefs.getString(res.getString(R.string.TUNNEL_INTERFACE), "127.0.0.1"));
if (!isStreamr)
cfg.setPort(prefs.getInt(res.getString(R.string.TUNNEL_LISTEN_PORT), -1));
}
@Override
protected void generalServerPort() {
cfg.setTargetPort(prefs.getInt(res.getString(R.string.TUNNEL_TARGET_PORT), -1));
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), "127.0.0.1"));
cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
}
}
@Override
protected void advanced() {
cfg.setTunnelDepth(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LENGTH),
res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH)));
cfg.setTunnelVariance(prefs.getInt(res.getString(R.string.TUNNEL_OPT_VARIANCE),
res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE)));
cfg.setTunnelQuantity(prefs.getInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
cfg.setTunnelBackupQuantity(prefs.getInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setProfile(prefs.getString(res.getString(R.string.TUNNEL_OPT_PROFILE), "bulk"));
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setConnectDelay(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_CONNECT), false));
}
@Override
protected void advancedServer() {
//cfg.setEncrypt(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_ENCRYPT), false));
//cfg.setEncryptKey(prefs.getString(res.getString(R.string.TUNNEL_OPT_ENCRYPT_KEY), ""));
cfg.setAccessMode(prefs.getInt(res.getString(R.string.TUNNEL_OPT_ACCESS_MODE), 0));
cfg.setAccessList(prefs.getString(res.getString(R.string.TUNNEL_OPT_ACCESS_LIST), ""));
cfg.setUniqueLocal(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_UNIQUE_LOCAL), false));
cfg.setMultihome(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_MULTIHOME), false));
cfg.setLimitMinute(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_MINUTE), 0));
cfg.setLimitHour(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_HOUR), 0));
cfg.setLimitDay(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_DAY), 0));
cfg.setTotalMinute(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_MINUTE), 0));
cfg.setTotalHour(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_HOUR), 0));
cfg.setTotalDay(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_DAY), 0));
cfg.setMaxStreams(prefs.getInt(res.getString(R.string.TUNNEL_OPT_MAX_STREAMS), 0));
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp) {
cfg.setRejectInproxy(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_REJECT_INPROXY), false));
cfg.setPostCheckTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_CHECK_TIME), 0));
cfg.setPostMax(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_MAX), 0));
cfg.setPostBanTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_BAN_TIME), 0));
cfg.setPostTotalMax(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_MAX), 0));
cfg.setPostTotalBanTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_BAN_TIME), 0));
}
}
@Override
protected void advancedIdle() {
cfg.setReduce(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_REDUCE_IDLE),
res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE)));
cfg.setReduceCount(prefs.getInt(res.getString(R.string.TUNNEL_OPT_REDUCE_QUANTITY),
res.getInteger(R.integer.DEFAULT_REDUCE_COUNT)));
cfg.setReduceTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_REDUCE_TIME),
res.getInteger(R.integer.DEFAULT_REDUCE_TIME)));
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setDelayOpen(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_OPEN),
res.getBoolean(R.bool.DEFAULT_DELAY_OPEN)));
}
@Override
protected void advancedClient() {
cfg.setClose(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_CLOSE_IDLE),
res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE)));
cfg.setCloseTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_CLOSE_TIME),
res.getInteger(R.integer.DEFAULT_CLOSE_TIME)));
cfg.setNewDest(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)) ? 2 :
prefs.getBoolean(res.getString(R.string.TUNNEL_OTP_NEW_KEYS), res.getBoolean(R.bool.DEFAULT_NEW_KEYS)) ? 1 : 0);
}
@Override
protected void advancedClientHttp() {
cfg.setAllowUserAgent(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_UA), false));
cfg.setAllowReferer(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_REFERER), false));
cfg.setAllowAccept(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_ACCEPT), false));
cfg.setAllowInternalSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_ALLOW_SSL), false));
cfg.setJumpList(prefs.getString(res.getString(R.string.TUNNEL_OPT_JUMP_LIST),
res.getString(R.string.DEFAULT_JUMP_LIST)));
}
@Override
protected void advancedClientProxy() {
cfg.setProxyAuth(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_LOCAL_AUTH), false) ? "digest" : "false");
String username = prefs.getString(res.getString(R.string.TUNNEL_OPT_LOCAL_USERNAME), "");
if (!username.isEmpty()) {
cfg.setProxyUsername(username);
cfg.setProxyPassword(prefs.getString(res.getString(R.string.TUNNEL_OPT_LOCAL_PASSWORD), ""));
}
cfg.setOutproxyAuth(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_OUTPROXY_AUTH), false));
cfg.setOutproxyUsername(prefs.getString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_USERNAME), ""));
cfg.setOutproxyPassword(prefs.getString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_PASSWORD), ""));
}
@Override
protected void advancedOther() {
cfg.setSigType(Integer.toString(prefs.getInt(res.getString(R.string.TUNNEL_OPT_SIGTYPE),
res.getInteger(R.integer.DEFAULT_SIGTYPE))));
cfg.setCustomOptions(prefs.getString(res.getString(R.string.TUNNEL_OPT_CUSTOM_OPTIONS), null));
}
}
public static TunnelConfig createConfigFromWizard(
Context ctx, TunnelControllerGroup tcg, Bundle data) {
return new TunnelUtil(tcg).createConfigFromWizard(ctx, data);
}
public TunnelConfig createConfigFromWizard(Context ctx, Bundle data) {
// Get the Bundle keys
Resources res = ctx.getResources();
// Create the TunnelConfig
TunnelConfig cfg = new TunnelConfig();
// Update the TunnelConfig from the tunnel wizard settings
String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
String kType = res.getString(R.string.i2ptunnel_wizard_k_type);
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
String type = getTypeFromName(typeName, ctx);
cfg.setType(type);
new TunnelConfigFromWizard(cfg, data, res, _group, type).runLogic();
return cfg;
}
class TunnelConfigFromWizard extends TunnelLogic {
TunnelConfig cfg;
Bundle data;
Resources res;
TunnelControllerGroup tcg;
public TunnelConfigFromWizard(TunnelConfig cfg, Bundle data, Resources res,
TunnelControllerGroup tcg, String type) {
super(type);
this.cfg = cfg;
this.data = data;
this.res = res;
this.tcg = tcg;
}
@Override
protected void general() {
cfg.setName(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_name)).getString(Page.SIMPLE_DATA_KEY));
cfg.setDescription(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_desc)).getString(Page.SIMPLE_DATA_KEY));
cfg.setStartOnLoad(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_auto_start)).getBoolean(Page.SIMPLE_DATA_KEY));
if (!isClient(mType) || res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY))
cfg.setPrivKeyFile(getPrivateKeyFile(tcg, -1));
}
@Override
protected void generalClient() {
// See advancedClient() for persistent key handling
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr)
cfg.setTargetHost(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_target_host)).getString(Page.SIMPLE_DATA_KEY));
else
cfg.setShared(res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS));
// Only set default tunnel parameters if this is not going to be a shared tunnel
if (isStreamr || res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS)) {
cfg.setTunnelDepth(res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH));
cfg.setTunnelVariance(res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE));
cfg.setTunnelQuantity(res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY));
cfg.setTunnelBackupQuantity(res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY));
}
}
@Override
protected void generalClientPort() {
cfg.setPort(Integer.parseInt(data.getBundle(
res.getString(R.string.i2ptunnel_wizard_k_binding_port)).getString(Page.SIMPLE_DATA_KEY)));
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setReachableBy(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_reachable_on)).getString(Page.SIMPLE_DATA_KEY));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy)
cfg.setProxyList(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_outproxies)).getString(Page.SIMPLE_DATA_KEY));
else
cfg.setTargetDestination(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_dest)).getString(Page.SIMPLE_DATA_KEY));
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (isHttp)
cfg.setSslProxies(null);
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
if (isStandardOrIrc)
cfg.setUseSSL(false);
}
@Override
protected void generalClientIrc() {
cfg.setDCC(false);
}
@Override
protected void generalServerHttp() {
cfg.setSpoofedHost(null);
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
cfg.setReachableBy(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_reachable_on)).getString(Page.SIMPLE_DATA_KEY));
if (!isStreamr)
cfg.setPort(Integer.parseInt(data.getBundle(
res.getString(R.string.i2ptunnel_wizard_k_binding_port)).getString(Page.SIMPLE_DATA_KEY)));
}
@Override
protected void generalServerPort() {
cfg.setTargetPort(Integer.parseInt(data.getBundle(
res.getString(R.string.i2ptunnel_wizard_k_target_port)).getString(Page.SIMPLE_DATA_KEY)));
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
cfg.setTargetHost(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_target_host)).getString(Page.SIMPLE_DATA_KEY));
cfg.setUseSSL(false);
}
}
@Override
protected void advanced() {
// Tunnel parameters handled in generalClientStreamr()
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setProfile("bulk");
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setConnectDelay(false);
}
@Override
protected void advancedServer() {
cfg.setEncrypt(false);
cfg.setAccessMode(0);
cfg.setUniqueLocal(false);
cfg.setMultihome(false);
cfg.setLimitMinute(0);
cfg.setLimitHour(0);
cfg.setLimitDay(0);
cfg.setTotalMinute(0);
cfg.setTotalHour(0);
cfg.setTotalDay(0);
cfg.setMaxStreams(0);
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp) {
cfg.setRejectInproxy(false);
cfg.setPostCheckTime(0);
cfg.setPostMax(0);
cfg.setPostBanTime(0);
cfg.setPostTotalMax(0);
cfg.setPostTotalBanTime(0);
}
}
@Override
protected void advancedIdle() {
cfg.setReduce(res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE));
cfg.setReduceCount(res.getInteger(R.integer.DEFAULT_REDUCE_COUNT));
cfg.setReduceTime(res.getInteger(R.integer.DEFAULT_REDUCE_TIME));
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setDelayOpen(res.getBoolean(R.bool.DEFAULT_DELAY_OPEN));
}
@Override
protected void advancedClient() {
cfg.setClose(res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE));
cfg.setCloseTime(res.getInteger(R.integer.DEFAULT_CLOSE_TIME));
cfg.setNewDest(res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY) ? 2 :
res.getBoolean(R.bool.DEFAULT_NEW_KEYS) ? 1 : 0);
}
@Override
protected void advancedClientHttp() {
cfg.setAllowUserAgent(false);
cfg.setAllowReferer(false);
cfg.setAllowAccept(false);
cfg.setAllowInternalSSL(false);
cfg.setJumpList(res.getString(R.string.DEFAULT_JUMP_LIST));
}
@Override
protected void advancedClientProxy() {
cfg.setProxyAuth("false");
cfg.setOutproxyAuth(false);
}
@Override
protected void advancedOther() {
cfg.setSigType(Integer.toString(res.getInteger(R.integer.DEFAULT_SIGTYPE)));
}
}
}

View File

@ -0,0 +1,59 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
private static final String PREFERENCE_CATEGORY_TRANSPORTS = "preference_category_transports";
private static final String PREFERENCE_CATEGORY_EXPL_TUNNELS = "preference_category_expl_tunnels";
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_advanced);
findPreference(PREFERENCE_CATEGORY_TRANSPORTS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_TRANSPORTS));
findPreference(PREFERENCE_CATEGORY_EXPL_TUNNELS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_EXPL_TUNNELS));
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_advanced);
}
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
private String category;
public CategoryClickListener(String category) {
this.category = category;
}
@Override
public boolean onPreferenceClick(Preference preference) {
Fragment fragment;
switch (category) {
case PREFERENCE_CATEGORY_TRANSPORTS:
fragment = new TransportsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_EXPL_TUNNELS:
fragment = new ExploratoryPoolPreferenceFragment();
break;
default:
throw new AssertionError();
}
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment, fragment)
.addToBackStack(null)
.commit();
return true;
}
}
}

View File

@ -0,0 +1,35 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class AppearancePreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_appearance);
}
@Override
public void onStart() {
super.onStart();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(
(SettingsActivity) getActivity()
);
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_appearance);
}
@Override
public void onStop() {
super.onStop();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
(SettingsActivity) getActivity()
);
}
}

View File

@ -0,0 +1,19 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class ExploratoryPoolPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_expl_tunnels);
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_exploratory_pool);
}
}

View File

@ -0,0 +1,107 @@
package net.i2p.android.preferences;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.stat.FrequencyStat;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.stat.StatManager;
import java.util.Map;
import java.util.SortedSet;
public class GraphsPreferenceFragment extends I2PreferenceFragment {
public static final String GRAPH_PREFERENCES_SEEN = "graphPreferencesSeen";
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings();
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.label_graphs);
}
private void setupGraphSettings() {
PreferenceScreen ps = getPreferenceScreen();
RouterContext ctx = Util.getRouterContext();
if (ctx == null) {
PreferenceCategory noRouter = new PreferenceCategory(getActivity());
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
} else if (StatSummarizer.instance() == null) {
PreferenceCategory noStats = new PreferenceCategory(getActivity());
noStats.setTitle(R.string.stats_not_ready);
ps.addPreference(noStats);
} else {
StatManager mgr = ctx.statManager();
Map<String, SortedSet<String>> all = mgr.getStatsByGroup();
for (String group : all.keySet()) {
SortedSet<String> stats = all.get(group);
if (stats.size() == 0) continue;
PreferenceCategory groupPrefs = new PreferenceCategory(getActivity());
groupPrefs.setKey("stat.groups." + group);
groupPrefs.setTitle(group);
ps.addPreference(groupPrefs);
for (String stat : stats) {
String key;
String description;
boolean canBeGraphed = false;
boolean currentIsGraphed = false;
RateStat rs = mgr.getRate(stat);
if (rs != null) {
description = rs.getDescription();
long period = rs.getPeriods()[0]; // should be the minimum
key = stat + "." + period;
if (period <= 10*60*1000) {
Rate r = rs.getRate(period);
canBeGraphed = r != null;
if (canBeGraphed) {
currentIsGraphed = r.getSummaryListener() != null;
}
}
} else {
FrequencyStat fs = mgr.getFrequency(stat);
if (fs != null) {
key = stat;
description = fs.getDescription();
// FrequencyStats cannot be graphed, but can be logged.
// XXX: Should log settings be here as well, or in a
// separate settings menu?
} else {
Util.e("Stat does not exist?! [" + stat + "]");
continue;
}
}
CheckBoxPreference statPref = new CheckBoxPreference(getActivity());
statPref.setKey("stat.summaries." + key);
statPref.setTitle(stat);
statPref.setSummary(description);
statPref.setEnabled(canBeGraphed);
statPref.setChecked(currentIsGraphed);
groupPrefs.addPreference(statPref);
}
}
// The user has now seen the current (possibly default) configuration
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
if (!prefs.getBoolean(GRAPH_PREFERENCES_SEEN, false))
prefs.edit()
.putBoolean(GRAPH_PREFERENCES_SEEN, true)
.apply();
}
}
}

View File

@ -0,0 +1,72 @@
package net.i2p.android.preferences;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.preferences.util.CustomPreferenceFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* A PreferenceFragment that handles saving router settings.
*/
public abstract class I2PreferenceFragment extends CustomPreferenceFragment {
@Override
public void onPause() {
List<Properties> lProps = Util.getPropertiesFromPreferences(getActivity());
Properties props = lProps.get(0);
Properties propsToRemove = lProps.get(1);
Properties logSettings = lProps.get(2);
Set toRemove = propsToRemove.keySet();
boolean restartRequired = Util.checkAndCorrectRouterConfig(getActivity(), props, toRemove);
// Apply new config if we are running.
RouterContext rCtx = Util.getRouterContext();
if (rCtx != null) {
rCtx.router().saveConfig(props, toRemove);
// Merge in new log settings
saveLoggingChanges(rCtx, logSettings);
} else {
// Merge in new config settings, write the file.
Util.mergeResourceToFile(getActivity(),
Util.getFileDir(getActivity()),
"router.config", R.raw.router_config, props, toRemove);
// Merge in new log settings
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
}
// Store the settings in Android
super.onPause();
if (restartRequired)
Toast.makeText(getActivity(), R.string.settings_router_restart_required, Toast.LENGTH_LONG).show();
}
private void saveLoggingChanges(I2PAppContext ctx, Properties logSettings) {
boolean shouldSave = false;
for (Object key : logSettings.keySet()) {
if ("logger.defaultLevel".equals(key)) {
String defaultLevel = (String) logSettings.get(key);
String oldDefault = ctx.logManager().getDefaultLimit();
if (!defaultLevel.equals(oldDefault)) {
shouldSave = true;
ctx.logManager().setDefaultLimit(defaultLevel);
}
}
}
if (shouldSave) {
ctx.logManager().saveConfig();
}
}
}

View File

@ -0,0 +1,53 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import android.support.v7.preference.PreferenceScreen;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.util.LogManager;
public class LoggingPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings();
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_logging);
}
private void setupLoggingSettings() {
PreferenceScreen ps = getPreferenceScreen();
RouterContext ctx = Util.getRouterContext();
if (ctx != null) {
LogManager mgr = ctx.logManager();
// Log level overrides
/*
StringBuilder buf = new StringBuilder(32*1024);
Properties limits = mgr.getLimits();
TreeSet<String> sortedLogs = new TreeSet<String>();
for (Iterator iter = limits.keySet().iterator(); iter.hasNext(); ) {
String prefix = (String)iter.next();
sortedLogs.add(prefix);
}
for (Iterator iter = sortedLogs.iterator(); iter.hasNext(); ) {
String prefix = (String)iter.next();
String level = limits.getProperty(prefix);
buf.append(prefix).append('=').append(level).append('\n');
}
*/
/* Don't show, there are no settings that require the router
} else {
PreferenceCategory noRouter = new PreferenceCategory(getActivity());
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
*/
}
}
}

View File

@ -0,0 +1,19 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class NetworkPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_net);
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_bandwidth_net);
}
}

View File

@ -0,0 +1,121 @@
package net.i2p.android.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
import net.i2p.android.preferences.util.PortPreference;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
public class TransportsPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
// Load any properties that the router might have changed on us.
loadProperties();
addPreferencesFromResource(R.xml.settings_transports);
setupTransportSettings();
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_transports);
}
private void loadProperties() {
Context context= getActivity();
RouterContext ctx = Util.getRouterContext();
if (ctx != null) {
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
int udpPort = ctx.getProperty(udpPortKey, -1);
int ntcpPort = ctx.getProperty(ntcpPortKey, -1);
boolean ntcpAutoPort = ctx.getBooleanPropertyDefaultTrue(ntcpAutoPortKey);
if (ntcpPort < 0 && ntcpAutoPort)
ntcpPort = udpPort;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getInt(udpPortKey, -1) != udpPort ||
prefs.getInt(ntcpPortKey, -1) != ntcpPort) {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(udpPortKey, udpPort);
editor.putInt(ntcpPortKey, ntcpPort);
// commit() instead of apply() because this needs to happen
// before AdvancedPreferenceFragment loads its Preferences.
editor.commit();
}
}
}
private void setupTransportSettings() {
final Context context= getActivity();
PreferenceScreen ps = getPreferenceScreen();
final String udpEnableKey = context.getString(R.string.PROP_ENABLE_UDP);
final String ntcpEnableKey = context.getString(R.string.PROP_ENABLE_NTCP);
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
final CheckBoxPreference udpEnable = (CheckBoxPreference) ps.findPreference(udpEnableKey);
final CheckBoxPreference ntcpEnable = (CheckBoxPreference) ps.findPreference(ntcpEnableKey);
final PortPreference udpPort = (PortPreference) ps.findPreference(udpPortKey);
final PortPreference ntcpPort = (PortPreference) ps.findPreference(ntcpPortKey);
final CheckBoxPreference ntcpAutoPort = (CheckBoxPreference) ps.findPreference(ntcpAutoPortKey);
udpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked || ntcpEnable.isChecked())
return true;
else {
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
return false;
}
}
});
ntcpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked || udpEnable.isChecked())
return true;
else {
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
return false;
}
}
});
udpPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (ntcpAutoPort.isChecked())
ntcpPort.setText((String) newValue);
return true;
}
});
ntcpAutoPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked)
ntcpPort.setText(udpPort.getText());
return true;
}
});
}
}

View File

@ -0,0 +1,73 @@
package net.i2p.android.preferences.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
import net.i2p.android.router.R;
public class ConnectionLimitPreference extends EditTextPreference {
private boolean mValueInTitle;
public ConnectionLimitPreference(Context context) {
this(context, null);
}
public ConnectionLimitPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ConnectionLimitPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
void init(Context context, AttributeSet attrs) {
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ConnectionLimitPreference, 0, 0);
mValueInTitle = attr.getBoolean(R.styleable.ConnectionLimitPreference_clp_valueInTitle, false);
attr.recycle();
}
@Override
public CharSequence getTitle() {
if (mValueInTitle)
return formatValue((String) super.getTitle());
else
return super.getTitle();
}
@Override
public CharSequence getSummary() {
if (mValueInTitle)
return super.getSummary();
else
return formatValue((String) super.getSummary());
}
private CharSequence formatValue(String format) {
String text = getText();
if ("0".equals(text))
text = getContext().getString(R.string.unlimited);
if (format == null)
format = "%s";
return String.format(format, text);
}
@Override
protected String getPersistedString(String defaultReturnValue) {
if(getSharedPreferences().contains(getKey())) {
int intValue = getPersistedInt(0);
return String.valueOf(intValue);
} else {
return defaultReturnValue;
}
}
@Override
protected boolean persistString(String value) {
return value != null && persistInt(Integer.valueOf(value));
}
}

View File

@ -0,0 +1,24 @@
package net.i2p.android.preferences.util;
import android.os.Bundle;
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
import android.text.InputType;
import android.view.View;
import android.widget.EditText;
public class ConnectionLimitPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
public static ConnectionLimitPreferenceDialog newInstance(String key) {
final ConnectionLimitPreferenceDialog fragment = new ConnectionLimitPreferenceDialog();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
((EditText)view.findViewById(android.R.id.edit)).setInputType(
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
}

View File

@ -0,0 +1,36 @@
package net.i2p.android.preferences.util;
import android.support.v4.app.DialogFragment;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
/**
* Handles custom Preferences.
*/
public abstract class CustomPreferenceFragment extends PreferenceFragmentCompat {
private static final String DIALOG_FRAGMENT_TAG =
"android.support.v7.preference.PreferenceFragment.DIALOG";
@Override
public void onDisplayPreferenceDialog(Preference preference) {
// check if dialog is already showing
if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
return;
}
DialogFragment f = null;
if (preference instanceof ConnectionLimitPreference) {
f = ConnectionLimitPreferenceDialog.newInstance(preference.getKey());
} else if (preference instanceof IntEditTextPreference) {
f = IntEditTextPreferenceDialog.newInstance(preference.getKey());
} else if (preference instanceof PortPreference) {
f = PortPreferenceDialog.newInstance(preference.getKey());
} else {
super.onDisplayPreferenceDialog(preference);
}
if (f != null) {
f.setTargetFragment(this, 0);
f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
}
}
}

View File

@ -1,39 +1,43 @@
package net.i2p.android.router.util;
package net.i2p.android.preferences.util;
import android.content.Context;
import android.preference.EditTextPreference;
import android.text.InputType;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
public class IntEditTextPreference extends EditTextPreference {
public IntEditTextPreference(Context context) {
super(context);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
public IntEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
@Override
public CharSequence getSummary() {
return String.format((String) super.getSummary(), getText());
String summary = (String) super.getSummary();
if (summary == null)
summary = "%s";
return String.format(summary, getText());
}
@Override
protected String getPersistedString(String defaultReturnValue) {
return String.valueOf(getPersistedInt(-1));
if(getSharedPreferences().contains(getKey())) {
int intValue = getPersistedInt(0);
return String.valueOf(intValue);
} else {
return defaultReturnValue;
}
}
@Override
protected boolean persistString(String value) {
return persistInt(Integer.valueOf(value));
return value != null && persistInt(Integer.valueOf(value));
}
}

View File

@ -0,0 +1,24 @@
package net.i2p.android.preferences.util;
import android.os.Bundle;
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
import android.text.InputType;
import android.view.View;
import android.widget.EditText;
public class IntEditTextPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
public static IntEditTextPreferenceDialog newInstance(String key) {
final IntEditTextPreferenceDialog fragment = new IntEditTextPreferenceDialog();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
((EditText)view.findViewById(android.R.id.edit)).setInputType(
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
}
}

View File

@ -0,0 +1,43 @@
package net.i2p.android.preferences.util;
import android.content.Context;
import android.support.v7.preference.ListPreference;
import android.util.AttributeSet;
public class IntListPreference extends ListPreference {
public IntListPreference(Context context) {
super(context);
}
public IntListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean persistString(String value) {
if (getSharedPreferences().contains(getKey())) {
try {
getPersistedInt(0);
} catch (ClassCastException e) {
// Fix for where this preference was previously stored in a ListPreference
getSharedPreferences().edit().remove(getKey()).commit();
}
}
return value != null && persistInt(Integer.valueOf(value));
}
@Override
protected String getPersistedString(String defaultReturnValue) {
if(getSharedPreferences().contains(getKey())) {
try {
int intValue = getPersistedInt(0);
return String.valueOf(intValue);
} catch (ClassCastException e) {
return super.getPersistedString("0");
}
} else {
return defaultReturnValue;
}
}
}

View File

@ -1,8 +1,7 @@
package net.i2p.android.router.util;
package net.i2p.android.preferences.util;
import android.content.Context;
import android.preference.EditTextPreference;
import android.text.InputType;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
import net.i2p.android.router.R;
@ -10,17 +9,14 @@ import net.i2p.android.router.R;
public class PortPreference extends EditTextPreference {
public PortPreference(Context context) {
super(context);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
}
public PortPreference(Context context, AttributeSet attrs) {
super(context, attrs);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
}
public PortPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
}
@Override

View File

@ -0,0 +1,23 @@
package net.i2p.android.preferences.util;
import android.os.Bundle;
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
import android.text.InputType;
import android.view.View;
import android.widget.EditText;
public class PortPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
public static PortPreferenceDialog newInstance(String key) {
final PortPreferenceDialog fragment = new PortPreferenceDialog();
final Bundle b = new Bundle(1);
b.putString(ARG_KEY, key);
fragment.setArguments(b);
return fragment;
}
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
((EditText)view.findViewById(android.R.id.edit)).setInputType(InputType.TYPE_CLASS_NUMBER);
}
}

View File

@ -0,0 +1,28 @@
package net.i2p.android.preferences.util;
import android.content.Context;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
public class SummaryEditTextPreference extends EditTextPreference {
public SummaryEditTextPreference(Context context) {
super(context);
}
public SummaryEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public CharSequence getSummary() {
String summary = (String) super.getSummary();
if (summary == null)
summary = "%s";
return String.format(summary, getText());
}
}

View File

@ -0,0 +1,132 @@
package net.i2p.android.router;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
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 net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
import net.i2p.android.router.dialog.AboutDialog;
import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.router.log.LogActivity;
import net.i2p.android.router.netdb.NetDbActivity;
import net.i2p.android.router.stats.PeersActivity;
import net.i2p.android.router.stats.RateGraphActivity;
import net.i2p.android.router.util.Util;
public class ConsoleContainer extends Fragment {
MainFragment mMainFragment = null;
FloatingActionsMenu mConsoleMenu;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.container_console, container, false);
// Start with the home view
if (savedInstanceState == null && getChildFragmentManager().findFragmentById(R.id.main_fragment) == null) {
mMainFragment = new MainFragment();
mMainFragment.setArguments(getActivity().getIntent().getExtras());
getChildFragmentManager().beginTransaction()
.add(R.id.main_fragment, mMainFragment).commit();
}
mConsoleMenu = (FloatingActionsMenu) v.findViewById(R.id.console_action_menu);
mConsoleMenu.findViewById(R.id.action_news).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent news = new Intent(getActivity(), NewsActivity.class);
startActivity(news);
}
});
mConsoleMenu.findViewById(R.id.action_logs).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent log = new Intent(getActivity(), LogActivity.class);
startActivity(log);
}
});
mConsoleMenu.findViewById(R.id.action_graphs).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent graphs = new Intent(getActivity(), RateGraphActivity.class);
startActivity(graphs);
}
});
mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent peers = new Intent(getActivity(), PeersActivity.class);
startActivity(peers);
}
});
mConsoleMenu.findViewById(R.id.action_netdb).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent netdb = new Intent(getActivity(), NetDbActivity.class);
startActivity(netdb);
}
});
setMenuVisibility();
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.activity_main_actions, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
setMenuVisibility();
}
private void setMenuVisibility() {
boolean routerRunning = Util.getRouterContext() != null;
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_graphs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
if (getActivity() != null) {
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean("i2pandroid.main.showStats", false);
mConsoleMenu.findViewById(R.id.action_peers).setVisibility(
advanced && routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
advanced && routerRunning ? View.VISIBLE : View.GONE);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_about:
AboutDialog dialog = new AboutDialog();
dialog.show(getFragmentManager(), "about");
return true;
case R.id.menu_help_release_notes:
TextResourceDialog rDdialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
rDdialog.setArguments(args);
rDdialog.show(getFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -1,477 +0,0 @@
package net.i2p.android.router;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import net.i2p.android.i2ptunnel.TunnelListActivity;
import net.i2p.android.router.addressbook.AddressbookActivity;
import net.i2p.android.router.log.LogActivity;
import net.i2p.android.router.netdb.NetDbActivity;
import net.i2p.android.router.service.RouterBinder;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.stats.PeersActivity;
import net.i2p.android.router.stats.RateGraphActivity;
import net.i2p.android.router.util.Util;
import net.i2p.android.router.web.WebActivity;
import net.i2p.android.router.web.WebFragment;
import net.i2p.router.RouterContext;
public abstract class I2PActivityBase extends ActionBarActivity implements
I2PFragmentBase.RouterContextProvider {
/**
* Navigation drawer variables
*/
protected DrawerLayout mDrawerLayout;
protected ListView mDrawerList;
protected ActionBarDrawerToggle mDrawerToggle;
private CharSequence mDrawerTitle;
private CharSequence mTitle;
/**
* Router variables
*/
protected boolean _isBound;
protected boolean _triedBind;
protected ServiceConnection _connection;
protected RouterService _routerService;
private SharedPreferences _sharedPrefs;
private static final String SHARED_PREFS = "net.i2p.android.router";
protected static final String PREF_AUTO_START = "autoStart";
/**
* true leads to a poor install experience, very slow to paint the screen
*/
protected static final boolean DEFAULT_AUTO_START = false;
protected static final String PREF_NAV_DRAWER_OPENED = "navDrawerOpened";
/**
* Override this in subclasses that need a ViewPager, such as a
* category view.
*
* @return whether this Activity needs a ViewPager.
*/
protected boolean useViewPager() {
return false;
}
/**
* Override this in subclasses that can use two panes, such as a
* list/detail class.
*
* @return whether this Activity can use a two-pane layout.
*/
protected boolean canUseTwoPanes() {
return false;
}
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
Util.d(this + " onCreate called");
super.onCreate(savedInstanceState);
_sharedPrefs = getSharedPreferences(SHARED_PREFS, 0);
// If the Activity wants to use a ViewPager, provide it.
// If the Activity can make use of two panes (if available),
// load the layout that will enable them. Otherwise, load the
// layout that will only ever have a single pane.
if (useViewPager())
setContentView(R.layout.activity_navdrawer_viewpager);
else if (canUseTwoPanes())
setContentView(R.layout.activity_navdrawer);
else
setContentView(R.layout.activity_navdrawer_onepane);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
mTitle = mDrawerTitle = getTitle();
String[] activityTitles = getResources().getStringArray(R.array.navdrawer_activity_titles);
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("i2pandroid.main.showStats", false)) {
String[] advActivityTitles = getResources().getStringArray(R.array.navdrawer_activity_titles_advanced);
String[] allTitles = new String[activityTitles.length + advActivityTitles.length];
System.arraycopy(activityTitles, 0, allTitles, 0, activityTitles.length);
System.arraycopy(advActivityTitles, 0, allTitles, activityTitles.length, advActivityTitles.length);
activityTitles = allTitles;
}
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.drawer);
// Set a custom shadow that overlays the main content when the drawer opens
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
mDrawerList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Set the adapter for the list view
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
R.layout.listitem_navdrawer, activityTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.string.drawer_open, R.string.drawer_close) {
private boolean wasDragged = false;
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
// Don't mark as opened if the user closed by dragging
// but uses the action bar icon to open
wasDragged = false;
getSupportActionBar().setTitle(mTitle);
supportInvalidateOptionsMenu();
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View view) {
if (wasDragged && !getPref(PREF_NAV_DRAWER_OPENED, false))
setPref(PREF_NAV_DRAWER_OPENED, true);
getSupportActionBar().setTitle(mDrawerTitle);
supportInvalidateOptionsMenu();
}
/** Called when the drawer motion state changes. */
public void onDrawerStateChanged(int newState) {
if (newState == DrawerLayout.STATE_DRAGGING)
wasDragged = true;
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
private class DrawerItemClickListener implements ListView.OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
selectItem(pos);
}
}
private void selectItem(int pos) {
switch (pos) {
case 1:
if (!(this instanceof NewsActivity)) {
Intent news = new Intent(I2PActivityBase.this, NewsActivity.class);
startActivity(news);
}
break;
case 2:
Intent ab = new Intent(I2PActivityBase.this, AddressbookActivity.class);
startActivity(ab);
break;
case 3:
Intent itb = new Intent(I2PActivityBase.this, TunnelListActivity.class);
startActivity(itb);
break;
case 4:
if (!(this instanceof LogActivity)) {
Intent log = new Intent(I2PActivityBase.this, LogActivity.class);
startActivity(log);
}
break;
case 5:
Intent wp = new Intent(I2PActivityBase.this, WebActivity.class);
wp.putExtra(WebFragment.HTML_RESOURCE_ID, R.raw.welcome_html);
startActivity(wp);
break;
case 6:
if (!(this instanceof RateGraphActivity)) {
Intent active = new Intent(I2PActivityBase.this, RateGraphActivity.class);
startActivity(active);
}
break;
case 7:
Intent peers = new Intent(I2PActivityBase.this, PeersActivity.class);
startActivity(peers);
break;
case 8:
if (!(this instanceof NetDbActivity)) {
Intent netdb = new Intent(I2PActivityBase.this, NetDbActivity.class);
startActivity(netdb);
}
break;
default:
Intent main = new Intent(I2PActivityBase.this, MainActivity.class);
startActivity(main);
break;
}
mDrawerLayout.closeDrawer(mDrawerList);
}
@Override
public void onRestart() {
Util.d(this + " onRestart called");
super.onRestart();
}
@Override
public void onStart() {
Util.d(this + " onStart called");
super.onStart();
if (_sharedPrefs.getBoolean(PREF_AUTO_START, DEFAULT_AUTO_START))
startRouter();
else
bindRouter(false);
}
/**
* @param def default
*/
public boolean getPref(String pref, boolean def) {
return _sharedPrefs.getBoolean(pref, def);
}
/**
* @param def default
*/
public String getPref(String pref, String def) {
return _sharedPrefs.getString(pref, def);
}
/**
* @return success
*/
public boolean setPref(String pref, boolean val) {
SharedPreferences.Editor edit = _sharedPrefs.edit();
edit.putBoolean(pref, val);
return edit.commit();
}
/**
* @return success
*/
public boolean setPref(String pref, String val) {
SharedPreferences.Editor edit = _sharedPrefs.edit();
edit.putString(pref, val);
return edit.commit();
}
@Override
public void onResume() {
Util.d(this + " onResume called");
super.onResume();
}
@Override
public void onPause() {
Util.d(this + " onPause called");
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
Util.d(this + " onSaveInstanceState called");
super.onSaveInstanceState(outState);
}
@Override
public void onStop() {
Util.d(this + " onStop called");
unbindRouter();
super.onStop();
}
@Override
public void onDestroy() {
Util.d(this + " onDestroy called");
super.onDestroy();
}
/**
* Called whenever we call invalidateOptionsMenu()
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// If the nav drawer is open, hide action items related to the content view
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
onDrawerChange(drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
/**
* Override in subclass with e.g.
* menu.findItem(R.id.action_add_to_addressbook).setVisible(!drawerOpen);
*
* @param drawerOpen true if the drawer is open
*/
protected void onDrawerChange(boolean drawerOpen) {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this.
if (mDrawerToggle.onOptionsItemSelected(item))
return true;
else if (item.getItemId() == android.R.id.home) {
// This happens when mDrawerToggle.setDrawerIndicatorEnabled(false)
onBackPressed();
}
// Handle action buttons and overflow
return super.onOptionsItemSelected(item);
}
@Override
public void setTitle(CharSequence title) {
mTitle = title;
getSupportActionBar().setTitle(mTitle);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggle
mDrawerToggle.onConfigurationChanged(newConfig);
}
public static class TabListener implements ActionBar.TabListener {
protected Fragment mFragment;
public TabListener(Fragment fragment) {
mFragment = fragment;
}
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.replace(R.id.main_fragment, mFragment);
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(mFragment);
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// User selected the already selected tab.
}
}
////// Service stuff
/**
* Start the service and bind to it
*/
protected boolean startRouter() {
Intent intent = new Intent();
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
Util.d(this + " calling startService");
ComponentName name = startService(intent);
if (name == null)
Util.d(this + " XXXXXXXXXXXXXXXXXXXX got null from startService!");
Util.d(this + " got from startService: " + name);
boolean success = bindRouter(true);
if (!success)
Util.d(this + " Bind router failed");
return success;
}
/**
* Bind only
*/
protected boolean bindRouter(boolean autoCreate) {
Intent intent = new Intent(RouterBinder.class.getName());
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
Util.d(this + " calling bindService");
_connection = new RouterConnection();
_triedBind = bindService(intent, _connection, autoCreate ? BIND_AUTO_CREATE : 0);
Util.d(this + " bindService: auto create? " + autoCreate + " success? " + _triedBind);
return _triedBind;
}
protected void unbindRouter() {
Util.d(this + " unbindRouter called with _isBound:" + _isBound + " _connection:" + _connection + " _triedBind:" + _triedBind);
if (_triedBind && _connection != null)
unbindService(_connection);
_triedBind = false;
_connection = null;
_routerService = null;
_isBound = false;
}
/**
* Class for interacting with the main interface of the RouterService.
*/
protected class RouterConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
Util.d(this + " connected to router service");
RouterBinder binder = (RouterBinder) service;
RouterService svc = binder.getService();
_routerService = svc;
_isBound = true;
onRouterBind(svc);
}
public void onServiceDisconnected(ComponentName name) {
Util.d(this + " disconnected from router service!!!!!!!");
// save memory
_routerService = null;
_isBound = false;
onRouterUnbind();
}
}
/**
* callback from ServiceConnection, override as necessary
*/
protected void onRouterBind(RouterService svc) {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (f instanceof I2PFragmentBase)
((I2PFragmentBase) f).onRouterBind();
else if (f instanceof I2PFragmentBase.RouterContextUser)
((I2PFragmentBase.RouterContextUser) f).onRouterBind();
if (canUseTwoPanes()) {
f = getSupportFragmentManager().findFragmentById(R.id.detail_fragment);
if (f instanceof I2PFragmentBase)
((I2PFragmentBase) f).onRouterBind();
else if (f instanceof I2PFragmentBase.RouterContextUser)
((I2PFragmentBase.RouterContextUser) f).onRouterBind();
}
}
/**
* callback from ServiceConnection, override as necessary
*/
protected void onRouterUnbind() {
}
// I2PFragmentBase.RouterContextProvider
public RouterContext getRouterContext() {
RouterService svc = _routerService;
if (svc == null || !_isBound)
return null;
return svc.getRouterContext();
}
}

View File

@ -1,5 +1,5 @@
package net.i2p.android.router;
public interface I2PConstants {
public static final String ANDROID_PREF_PREFIX = "i2pandroid.";
String ANDROID_PREF_PREFIX = "i2pandroid.";
}

View File

@ -1,5 +1,9 @@
package net.i2p.android.router;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import net.i2p.android.router.util.Util;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.NetworkDatabaseFacade;
import net.i2p.router.Router;
@ -8,40 +12,12 @@ 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);
@ -64,7 +40,7 @@ public class I2PFragmentBase extends Fragment {
public void onRouterConnectionNotReady() {}
protected RouterContext getRouterContext() {
return mCallback.getRouterContext();
return Util.getRouterContext();
}
protected Router getRouter() {

View File

@ -1,13 +1,18 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import net.i2p.android.I2PActivityBase;
public class LicenseActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
setContentView(R.layout.activity_onepane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Start with the base view
if (savedInstanceState == null) {
LicenseFragment f = new LicenseFragment();

View File

@ -5,7 +5,7 @@ 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 {
@ -28,7 +28,7 @@ public class LicenseFragment extends ListFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, names));
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
}
@Override

View File

@ -1,318 +0,0 @@
package net.i2p.android.router;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.dialog.AboutDialog;
import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.router.service.IRouterState;
import net.i2p.android.router.service.IRouterStateCallback;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import java.io.File;
import java.lang.ref.WeakReference;
public class MainActivity extends I2PActivityBase implements
MainFragment.RouterControlListener {
IRouterState mStateService = null;
MainFragment mMainFragment = null;
private boolean mAutoStartFromIntent = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start with the home view
if (savedInstanceState == null) {
mMainFragment = new MainFragment();
mMainFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, mMainFragment).commit();
}
// Open nav drawer if the user has never opened it themselves
if (!getPref(PREF_NAV_DRAWER_OPENED, false))
mDrawerLayout.openDrawer(mDrawerList);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
Util.d("Initializing...");
InitActivities init = new InitActivities(this);
init.debugStuff();
init.initialize();
super.onPostCreate(savedInstanceState);
handleIntents();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntents();
}
private void handleIntents() {
if (getIntent() == null)
return;
Intent intent = getIntent();
String action = intent.getAction();
if (action == null)
return;
if (action.equals("net.i2p.android.router.START_I2P")) {
autoStart();
}
}
private void autoStart() {
if (canStart()) {
if (Connectivity.isConnected(this)) {
mAutoStartFromIntent = true;
onStartRouterClicked();
} else {
// Not connected to a network
// TODO: Notify user
}
} else {
// TODO: Notify user
}
}
@Override
public void onResume() {
super.onResume();
if (mStateService != null) {
try {
if (mStateService.isStarted()) {
// Update for the current state.
Util.d("Fetching state.");
State curState = mStateService.getState();
Message msg = mHandler.obtainMessage(STATE_MSG);
msg.getData().putParcelable(MSG_DATA, curState);
mHandler.sendMessage(msg);
} else {
Util.d("StateService not started yet");
}
} catch (RemoteException e) {}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_main_actions, menu);
inflater.inflate(R.menu.activity_base_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
return true;
case R.id.menu_about:
AboutDialog dialog = new AboutDialog();
dialog.show(getSupportFragmentManager(), "about");
return true;
case R.id.menu_help:
Intent hi = new Intent(MainActivity.this, HelpActivity.class);
startActivity(hi);
return true;
case R.id.menu_help_release_notes:
TextResourceDialog rDdialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
rDdialog.setArguments(args);
rDdialog.show(getSupportFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onStop() {
if (mStateService != null) {
try {
mStateService.unregisterCallback(mStateCallback);
} catch (RemoteException e) {}
}
if (mTriedBindState)
unbindService(mStateConnection);
mTriedBindState = false;
super.onStop();
}
@Override
protected void onRouterBind(RouterService svc) {
if (mStateService == null) {
// Try binding for state updates.
// Don't auto-create the RouterService.
Intent intent = new Intent(IRouterState.class.getName());
intent.setClassName(this, "net.i2p.android.router.service.RouterService");
mTriedBindState = bindService(intent,
mStateConnection, 0);
Util.d("Bind to IRouterState successful: " + mTriedBindState);
}
super.onRouterBind(svc);
}
private boolean mTriedBindState;
private ServiceConnection mStateConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mStateService = IRouterState.Stub.asInterface(service);
Util.d("StateService bound");
try {
if (mStateService.isStarted()) {
mStateService.registerCallback(mStateCallback);
// Update for the current state.
Util.d("Fetching state.");
State curState = mStateService.getState();
Message msg = mHandler.obtainMessage(STATE_MSG);
msg.getData().putParcelable(MSG_DATA, curState);
mHandler.sendMessage(msg);
} else {
// Unbind
unbindService(mStateConnection);
mStateService = null;
mTriedBindState = false;
}
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mStateService = null;
}
};
private IRouterStateCallback mStateCallback = new IRouterStateCallback.Stub() {
/**
* This is called by the RouterService regularly to tell us about
* new states. Note that IPC calls are dispatched through a thread
* pool running in each process, so the code executing here will
* NOT be running in our main thread like most other things -- so,
* to update the UI, we need to use a Handler to hop over there.
*/
public void stateChanged(State newState) throws RemoteException {
Message msg = mHandler.obtainMessage(STATE_MSG);
msg.getData().putParcelable(MSG_DATA, newState);
mHandler.sendMessage(msg);
}
};
private static final int STATE_MSG = 1;
private static final String MSG_DATA = "state";
private Handler mHandler = new StateHandler(new WeakReference<>(this));
private static class StateHandler extends Handler {
WeakReference<MainActivity> mReference;
public StateHandler(WeakReference<MainActivity> reference) {
mReference = reference;
}
private State lastRouterState = null;
@Override
public void handleMessage(Message msg) {
MainActivity parent = mReference.get();
if (parent == null)
return;
switch (msg.what) {
case STATE_MSG:
State state = msg.getData().getParcelable(MSG_DATA);
if (lastRouterState == null || lastRouterState != state) {
if (parent.mMainFragment == null)
parent.mMainFragment = (MainFragment) parent.getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (parent.mMainFragment != null) {
parent.mMainFragment.updateState(state);
lastRouterState = state;
}
if (state == State.RUNNING && parent.mAutoStartFromIntent) {
parent.setResult(RESULT_OK);
parent.finish();
}
}
break;
default:
super.handleMessage(msg);
}
}
}
private boolean canStart() {
RouterService svc = _routerService;
return (svc == null) || (!_isBound) || svc.canManualStart();
}
private boolean canStop() {
RouterService svc = _routerService;
return svc != null && _isBound && svc.canManualStop();
}
// MainFragment.RouterControlListener
public boolean shouldShowOnOff() {
return (canStart() && Connectivity.isConnected(this)) || canStop();
}
public boolean shouldBeOn() {
String action = getIntent().getAction();
return (canStop()) ||
(action != null && action.equals("net.i2p.android.router.START_I2P"));
}
public void onStartRouterClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, true);
svc.manualStart();
} else {
(new File(Util.getFileDir(this), "wrapper.log")).delete();
startRouter();
}
}
public boolean onStopRouterClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.manualQuit();
return true;
}
return false;
}
}

View File

@ -1,15 +1,22 @@
package net.i2p.android.router;
import android.app.Activity;
import android.graphics.Color;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@ -18,9 +25,10 @@ import android.widget.TableRow;
import android.widget.TextView;
import android.widget.ToggleButton;
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.router.dialog.FirstStartDialog;
import net.i2p.android.router.dialog.VersionDialog;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.LongToggleButton;
@ -31,10 +39,8 @@ 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;
@ -46,8 +52,22 @@ public class MainFragment extends I2PFragmentBase {
private Runnable _updater;
private Runnable _oneShotUpdate;
private String _savedStatus;
private boolean _keep = true;
private boolean _startPressed = false;
private ImageView mConsoleLights;
private LongToggleButton mOnOffButton;
private LinearLayout vGracefulButtons;
private ScrollView mScrollView;
private View vStatusContainer;
private ImageView vNetStatusLevel;
private TextView vNetStatusText;
private View vNonNetStatus;
private TextView vUptime;
private TextView vActive;
private TextView vKnown;
private TableLayout vTunnels;
private LinearLayout vAdvStatus;
private TextView vAdvStatusText;
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
private static final String PREF_FIRST_START = "app.router.firstStart";
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
@ -57,10 +77,28 @@ public class MainFragment extends I2PFragmentBase {
// Container Activity must implement this interface
public interface RouterControlListener {
public boolean shouldShowOnOff();
public boolean shouldBeOn();
public void onStartRouterClicked();
public boolean onStopRouterClicked();
boolean shouldShowOnOff();
boolean shouldBeOn();
void onStartRouterClicked();
boolean onStopRouterClicked();
/**
* @since 0.9.19
*/
boolean isGracefulShutdownInProgress();
/**
* @since 0.9.19
*/
boolean onGracefulShutdownClicked();
/**
* @since 0.9.19
*/
boolean onCancelGracefulShutdownClicked();
}
@Override
@ -85,15 +123,14 @@ public class MainFragment extends I2PFragmentBase {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Init stuff here so settings work.
if(savedInstanceState != null) {
if (savedInstanceState != null) {
lastRouterState = savedInstanceState.getParcelable("lastState");
String saved = savedInstanceState.getString("status");
if(saved != null) {
if (saved != null) {
_savedStatus = saved;
}
}
_keep = true;
_handler = new Handler();
_updater = new Updater();
_oneShotUpdate = new OneShotUpdate();
@ -104,24 +141,54 @@ public class MainFragment extends I2PFragmentBase {
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);
mConsoleLights = (ImageView) v.findViewById(R.id.console_lights);
mOnOffButton = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
vGracefulButtons = (LinearLayout) v.findViewById(R.id.router_graceful_buttons);
mScrollView = (ScrollView) v.findViewById(R.id.main_scrollview);
vStatusContainer = v.findViewById(R.id.status_container);
vNetStatusLevel = (ImageView) v.findViewById(R.id.console_net_status_level);
vNetStatusText = (TextView) v.findViewById(R.id.console_net_status_text);
vNonNetStatus = v.findViewById(R.id.console_non_net_status_container);
vUptime = (TextView) v.findViewById(R.id.console_uptime);
vActive = (TextView) v.findViewById(R.id.console_active);
vKnown = (TextView) v.findViewById(R.id.console_known);
vTunnels = (TableLayout) v.findViewById(R.id.main_tunnels);
vAdvStatus = (LinearLayout) v.findViewById(R.id.console_advanced_status);
vAdvStatusText = (TextView) v.findViewById(R.id.console_advanced_status_text);
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
b.setOnLongClickListener(new View.OnLongClickListener() {
updateState(lastRouterState);
mOnOffButton.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()) {
} else if (mCallback.onGracefulShutdownClicked())
updateOneShot();
return true;
}
});
Button gb = (Button) v.findViewById(R.id.button_shutdown_now);
gb.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress())
if (mCallback.onStopRouterClicked())
updateOneShot();
return true;
}
});
gb = (Button) v.findViewById(R.id.button_cancel_graceful);
gb.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress())
if (mCallback.onCancelGracefulShutdownClicked())
updateOneShot();
}
}
return true;
}
});
@ -134,19 +201,46 @@ public class MainFragment extends I2PFragmentBase {
super.onStart();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
if(_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
if (_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.console_advanced_status_text);
tv.setText(_savedStatus);
}
checkDialog();
_handler.postDelayed(_updater, 100);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
lbm.sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
}
private State lastRouterState;
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (lastRouterState == null || lastRouterState != state) {
updateState(state);
// If we have stopped, clear the status info immediately
if (Util.isStopped(state)) {
updateOneShot();
}
lastRouterState = state;
}
}
};
@Override
public void onStop() {
super.onStop();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
}
@Override
@ -157,9 +251,10 @@ public class MainFragment extends I2PFragmentBase {
@Override
public void onSaveInstanceState(Bundle outState) {
if(_savedStatus != null) {
if (lastRouterState != null)
outState.putParcelable("lastState", lastRouterState);
if (_savedStatus != null)
outState.putString("status", _savedStatus);
}
super.onSaveInstanceState(outState);
}
@ -171,7 +266,12 @@ public class MainFragment extends I2PFragmentBase {
public void run() {
updateVisibility();
updateStatus();
try {
updateStatus();
} catch (NullPointerException npe) {
// RouterContext wasn't quite ready
Util.w("Status was updated before RouterContext was ready", npe);
}
}
}
@ -180,10 +280,16 @@ public class MainFragment extends I2PFragmentBase {
private int counter;
private final int delay = 1000;
private final int toloop = delay / 500;
public void run() {
updateVisibility();
if(counter++ % toloop == 0) {
updateStatus();
if (counter++ % toloop == 0) {
try {
updateStatus();
} catch (NullPointerException npe) {
// RouterContext wasn't quite ready
Util.w("Status was updated before RouterContext was ready", npe);
}
}
//_handler.postDelayed(this, 2500);
_handler.postDelayed(this, delay);
@ -192,101 +298,91 @@ public class MainFragment extends I2PFragmentBase {
private void updateVisibility() {
boolean showOnOff = mCallback.shouldShowOnOff();
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
b.setVisibility(showOnOff ? View.VISIBLE : View.INVISIBLE);
mOnOffButton.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
boolean isOn = mCallback.shouldBeOn();
b.setChecked(isOn);
mOnOffButton.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 = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
Util.d("*********************************************************");
Util.d("Back pressed, Keep? " + _keep);
Util.d("*********************************************************");
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
if(!_keep) {
Thread t = new Thread(new KillMe());
t.start();
}
}
private class KillMe implements Runnable {
public void run() {
Util.d("*********************************************************");
Util.d("KillMe started!");
Util.d("*********************************************************");
try {
Thread.sleep(500); // is 500ms long enough?
} catch(InterruptedException ex) {
boolean isGraceful = mCallback.isGracefulShutdownInProgress();
vGracefulButtons.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
if (isOn && isGraceful) {
RouterContext ctx = getRouterContext();
if (ctx != null) {
TextView tv = (TextView) vGracefulButtons.findViewById(R.id.router_graceful_status);
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
tv.setText(getActivity().getResources().getString(R.string.button_router_graceful,
DataHelper.formatDuration(ms)));
} else {
tv.setText(getActivity().getString(R.string.notification_status_stopping));
}
}
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);
mConsoleLights.setImageResource(R.drawable.routerlogo_0);
} else if (newState == State.STARTING ||
//newState == State.GRACEFUL_SHUTDOWN || // Don't change lights for graceful
newState == State.STOPPING ||
newState == State.MANUAL_STOPPING ||
newState == State.MANUAL_QUITTING ||
newState == State.NETWORK_STOPPING) {
lightImage.setImageResource(R.drawable.routerlogo_1);
mConsoleLights.setImageResource(R.drawable.routerlogo_1);
} else if (newState == State.RUNNING) {
lightImage.setImageResource(R.drawable.routerlogo_2);
mConsoleLights.setImageResource(R.drawable.routerlogo_2);
} else if (newState == State.ACTIVE) {
lightImage.setImageResource(R.drawable.routerlogo_3);
mConsoleLights.setImageResource(R.drawable.routerlogo_3);
} else if (newState == State.WAITING) {
lightImage.setImageResource(R.drawable.routerlogo_4);
mConsoleLights.setImageResource(R.drawable.routerlogo_4);
} // Ignore unknown states.
}
private void updateStatus() {
RouterContext ctx = getRouterContext();
ScrollView sv = (ScrollView) getActivity().findViewById(R.id.main_scrollview);
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
if(!Connectivity.isConnected(getActivity())) {
if (!Connectivity.isConnected(getActivity())) {
// Manually set state, RouterService won't be running
updateState(State.WAITING);
vStatusText.setText("No Internet connection is available");
vStatus.setVisibility(View.VISIBLE);
sv.setVisibility(View.VISIBLE);
} else if(ctx != null) {
if(_startPressed) {
_startPressed = false;
vNetStatusText.setText(R.string.no_internet);
vStatusContainer.setVisibility(View.VISIBLE);
vNonNetStatus.setVisibility(View.GONE);
} else if (lastRouterState != null &&
!Util.isStopping(lastRouterState) &&
!Util.isStopped(lastRouterState) &&
ctx != null) {
Util.NetStatus netStatus = Util.getNetStatus(getActivity(), ctx);
switch (netStatus.level) {
case ERROR:
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_error_red_24dp));
vNetStatusLevel.setVisibility(View.VISIBLE);
break;
case WARN:
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_warning_amber_24dp));
vNetStatusLevel.setVisibility(View.VISIBLE);
break;
case INFO:
default:
vNetStatusLevel.setVisibility(View.GONE);
}
vNetStatusText.setText(getString(R.string.settings_label_network) + ": " + netStatus.status);
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
int active = ctx.commSystem().countActivePeers();
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
vUptime.setText("" + uptime);
vActive.setText("" + active);
vKnown.setText("" + known);
// 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();
@ -295,80 +391,52 @@ public class MainFragment extends I2PFragmentBase {
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;
"Exploratory 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 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())
"\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
+ "\nJob Lag: " + jobLag
+ "\nMsg Delay: " + msgDelay
+ "\nUptime: " + uptime;
+ "\nMsg Delay: " + msgDelay;
_savedStatus = status + participate + details;
vStatusText.setText(_savedStatus);
vStatus.setVisibility(View.VISIBLE);
vAdvStatusText.setText(_savedStatus);
vAdvStatus.setVisibility(View.VISIBLE);
} else {
vStatus.setVisibility(View.GONE);
vAdvStatus.setVisibility(View.GONE);
}
sv.setVisibility(View.VISIBLE);
vStatusContainer.setVisibility(View.VISIBLE);
vNonNetStatus.setVisibility(View.VISIBLE);
// Usage stats in bottom toolbar
double inBw = ctx.bandwidthLimiter().getReceiveBps();
double outBw = ctx.bandwidthLimiter().getSendBps();
double inData = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes();
double outData = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes();
((TextView) getActivity().findViewById(R.id.console_download_stats)).setText(
Util.formatSpeed(inBw) + "Bps / " + Util.formatSize(inData) + "B");
((TextView) getActivity().findViewById(R.id.console_upload_stats)).setText(
Util.formatSpeed(outBw) + "Bps / " + Util.formatSize(outData) + "B");
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.VISIBLE);
} else {
// network but no router context
vStatusText.setText("Not running");
sv.setVisibility(View.INVISIBLE);
vStatusContainer.setVisibility(View.GONE);
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.INVISIBLE);
/**
* **
* RouterService svc = _routerService; String status = "connected? "
@ -382,21 +450,24 @@ public class MainFragment extends I2PFragmentBase {
* "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();
vTunnels.removeAllViews();
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients());
if (!clients.isEmpty()) {
List<Destination> clients = null;
if (ctx.clientManager() != null)
clients = new ArrayList<Destination>(ctx.clientManager().listClients());
if (clients != null && !clients.isEmpty()) {
Collections.sort(clients, new AlphaComparator(ctx));
for (Destination client : clients) {
String name = getName(ctx, client);
@ -438,25 +509,29 @@ public class MainFragment extends I2PFragmentBase {
type.setBackgroundResource(R.drawable.tunnel_yellow);
}
dests.addView(dest);
vTunnels.addView(dest);
}
} else {
TableRow empty = new TableRow(getActivity());
TextView emptyText = new TextView(getActivity());
emptyText.setText(R.string.no_client_tunnels_running);
emptyText.setText(R.string.no_tunnels_running);
empty.addView(emptyText);
dests.addView(empty);
vTunnels.addView(empty);
}
}
/** compare translated nicknames - put "shared clients" first in the sort */
private static final String SHARED_CLIENTS = "shared clients";
/**
* compare translated nicknames - put "shared clients" first in the sort
*/
private class AlphaComparator implements Comparator<Destination> {
private String xsc;
private RouterContext _ctx;
public AlphaComparator(RouterContext ctx) {
_ctx = ctx;
xsc = _(ctx, "shared clients");
xsc = _(ctx, SHARED_CLIENTS);
}
public int compare(Destination lhs, Destination rhs) {
@ -470,34 +545,85 @@ public class MainFragment extends I2PFragmentBase {
}
}
/** translate here so collation works above */
/**
* 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);
}
if (name == null)
name = d.calculateHash().toBase64().substring(0, 6);
else
name = _(ctx, name);
return name;
}
private String _(RouterContext ctx, String s) {
return Translate.getString(s, ctx, "net.i2p.router.web.messages");
if (SHARED_CLIENTS.equals(s))
return getString(R.string.shared_clients);
else
return s;
}
private void checkDialog() {
I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
if (configureBrowser) {
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
ab.setPref(PREF_CONFIGURE_BROWSER, false);
final I2PActivityBase ab = (I2PActivityBase) getActivity();
String language = PreferenceManager.getDefaultSharedPreferences(ab).getString(
getString(R.string.PREF_LANGUAGE), null
);
if (language == null) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.choose_language)
.setItems(R.array.language_names, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Save the language choice
String language = getResources().getStringArray(R.array.languages)[which];
PreferenceManager.getDefaultSharedPreferences(getActivity())
.edit()
.putString(getString(R.string.PREF_LANGUAGE), language)
.commit();
// Close the dialog
dialog.dismiss();
// Broadcast the change to RouterService just in case the router is running
Intent intent = new Intent(RouterService.LOCAL_BROADCAST_LOCALE_CHANGED);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
// Update the parent
ab.notifyLocaleChanged();
// Run checkDialog() again to show the next dialog
// (if the change doesn't restart the Activity)
checkDialog();
}
})
.setCancelable(false)
.show();
} else if (ab.getPref(PREF_CONFIGURE_BROWSER, true)) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_browser_title)
.setMessage(R.string.configure_browser_for_i2p)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
ab.setPref(PREF_CONFIGURE_BROWSER, false);
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
ab.setPref(PREF_CONFIGURE_BROWSER, false);
}
})
.show();
}
/*VersionDialog dialog = new VersionDialog();
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");

View File

@ -1,13 +1,20 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import net.i2p.android.I2PActivityBase;
public class NewsActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
setContentView(R.layout.activity_onepane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Start with the base view
if (savedInstanceState == null) {
NewsFragment f = new NewsFragment();

View File

@ -1,286 +1,178 @@
package net.i2p.android.router;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.util.IntEditTextPreference;
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.android.I2PActivity;
import net.i2p.android.preferences.AdvancedPreferenceFragment;
import net.i2p.android.preferences.AppearancePreferenceFragment;
import net.i2p.android.preferences.GraphsPreferenceFragment;
import net.i2p.android.preferences.LoggingPreferenceFragment;
import net.i2p.android.preferences.NetworkPreferenceFragment;
import net.i2p.android.router.addressbook.AddressbookSettingsActivity;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.util.LocaleManager;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
public class SettingsActivity extends AppCompatActivity implements
SharedPreferences.OnSharedPreferenceChangeListener {
public static final String PREFERENCE_CATEGORY = "preference_category";
public static final String PREFERENCE_CATEGORY_NETWORK = "preference_category_network";
public static final String PREFERENCE_CATEGORY_GRAPHS = "preference_category_graphs";
public static final String PREFERENCE_CATEGORY_LOGGING = "preference_category_logging";
public static final String PREFERENCE_CATEGORY_ADDRESSBOOK = "preference_category_addressbook";
public static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
public static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
public class SettingsActivity extends PreferenceActivity {
// Actions for legacy settings
private static final String ACTION_PREFS_NET = "net.i2p.android.router.PREFS_NET";
public static final String ACTION_PREFS_GRAPHS = "net.i2p.android.router.PREFS_GRAPHS";
private static final String ACTION_PREFS_LOGGING = "net.i2p.android.router.PREFS_LOGGING";
private static final String ACTION_PREFS_ADVANCED = "net.i2p.android.router.PREFS_ADVANCED";
private final LocaleManager localeManager = new LocaleManager();
private Toolbar mToolbar;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);
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(), Util.getRouterContext());
} else if (ACTION_PREFS_LOGGING.equals(action)) {
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
} else if (ACTION_PREFS_ADVANCED.equals(action)) {
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(this, getPreferenceScreen(), Util.getRouterContext());
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.settings_headers_legacy);
}
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setTitle(getTitle());
}
Fragment fragment;
String category = getIntent().getStringExtra(PREFERENCE_CATEGORY);
if (category != null)
fragment = getFragmentForCategory(category);
else
fragment = new SettingsFragment();
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
if (ctx == null) {
PreferenceCategory noRouter = new PreferenceCategory(context);
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
} else if (StatSummarizer.instance() == null) {
PreferenceCategory noStats = new PreferenceCategory(context);
noStats.setTitle(R.string.stats_not_ready);
ps.addPreference(noStats);
} else {
StatManager mgr = ctx.statManager();
Map<String, SortedSet<String>> all = mgr.getStatsByGroup();
for (String group : all.keySet()) {
SortedSet<String> stats = all.get(group);
if (stats.size() == 0) continue;
PreferenceCategory groupPrefs = new PreferenceCategory(context);
groupPrefs.setKey("stat.groups." + group);
groupPrefs.setTitle(group);
ps.addPreference(groupPrefs);
for (String stat : stats) {
String key;
String description;
boolean canBeGraphed = false;
boolean currentIsGraphed = false;
RateStat rs = mgr.getRate(stat);
if (rs != null) {
description = rs.getDescription();
long period = rs.getPeriods()[0]; // should be the minimum
key = stat + "." + period;
if (period <= 10*60*1000) {
Rate r = rs.getRate(period);
canBeGraphed = r != null;
if (canBeGraphed) {
currentIsGraphed = r.getSummaryListener() != null;
}
}
} else {
FrequencyStat fs = mgr.getFrequency(stat);
if (fs != null) {
key = stat;
description = fs.getDescription();
// FrequencyStats cannot be graphed, but can be logged.
// XXX: Should log settings be here as well, or in a
// separate settings menu?
} else {
Util.e("Stat does not exist?! [" + stat + "]");
continue;
}
}
CheckBoxPreference statPref = new CheckBoxPreference(context);
statPref.setKey("stat.summaries." + key);
statPref.setTitle(stat);
statPref.setSummary(description);
statPref.setEnabled(canBeGraphed);
statPref.setChecked(currentIsGraphed);
groupPrefs.addPreference(statPref);
}
}
}
}
protected static void setupLoggingSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
if (ctx != null) {
LogManager mgr = ctx.logManager();
// Log level overrides
/*
StringBuilder buf = new StringBuilder(32*1024);
Properties limits = mgr.getLimits();
TreeSet<String> sortedLogs = new TreeSet<String>();
for (Iterator iter = limits.keySet().iterator(); iter.hasNext(); ) {
String prefix = (String)iter.next();
sortedLogs.add(prefix);
}
for (Iterator iter = sortedLogs.iterator(); iter.hasNext(); ) {
String prefix = (String)iter.next();
String level = limits.getProperty(prefix);
buf.append(prefix).append('=').append(level).append('\n');
}
*/
/* Don't show, there are no settings that require the router
} else {
PreferenceCategory noRouter = new PreferenceCategory(context);
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
*/
}
}
protected static void setupAdvancedSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
if (ctx != null) {
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
IntEditTextPreference udpPort = (IntEditTextPreference) ps.findPreference(udpPortKey);
IntEditTextPreference ntcpPort = (IntEditTextPreference) ps.findPreference(ntcpPortKey);
String udpCurrentPort = ctx.getProperty(udpPortKey, "0");
udpPort.setText(udpCurrentPort);
String ntcpCurrentPort = ctx.getProperty(ntcpPortKey);
if (ntcpCurrentPort != null && ntcpCurrentPort.length() > 0)
ntcpPort.setText(ntcpCurrentPort);
else
ntcpPort.setText(udpCurrentPort);
}
}
@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);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment, fragment)
.commit();
}
@Override
public void setContentView(int layoutResID) {
ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate(
R.layout.activity_settings,
(ViewGroup) getWindow().getDecorView().getRootView(), false);
mToolbar = (Toolbar) contentView.findViewById(R.id.main_toolbar);
mToolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
ViewGroup contentWrapper = (ViewGroup) contentView.findViewById(R.id.content_wrapper);
LayoutInflater.from(this).inflate(layoutResID, contentWrapper, true);
getWindow().setContentView(contentView);
public void onResume() {
super.onResume();
localeManager.onResume(this);
}
@Override
protected void onPause() {
List<Properties> lProps = Util.getPropertiesFromPreferences(this);
Properties props = lProps.get(0);
Properties propsToRemove = lProps.get(1);
Properties logSettings = lProps.get(2);
Set toRemove = propsToRemove.keySet();
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props, toRemove);
// Apply new config if we are running.
RouterContext rCtx = Util.getRouterContext();
if (rCtx != null) {
rCtx.router().saveConfig(props, toRemove);
// Merge in new log settings
saveLoggingChanges(rCtx, logSettings);
public boolean onSupportNavigateUp() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else {
// Merge in new config settings, write the file.
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props, toRemove);
// Merge in new log settings
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
Intent intent = new Intent(this, I2PActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
// Store the settings in Android
super.onPause();
if (restartRequired)
Toast.makeText(this, R.string.settings_router_restart_required, Toast.LENGTH_LONG).show();
return true;
}
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();
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getResources().getString(R.string.PREF_LANGUAGE))) {
localeManager.onResume(this);
Intent intent = new Intent(RouterService.LOCAL_BROADCAST_LOCALE_CHANGED);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class SettingsFragment extends PreferenceFragment {
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(Bundle paramBundle, String s) {
migrateOldSettings();
String settings = getArguments().getString("settings");
if ("net".equals(settings)) {
addPreferencesFromResource(R.xml.settings_net);
} else if ("graphs".equals(settings)) {
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
} else if ("logging".equals(settings)) {
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
} else if ("advanced".equals(settings)) {
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
addPreferencesFromResource(R.xml.settings);
this.findPreference(PREFERENCE_CATEGORY_NETWORK)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NETWORK));
this.findPreference(PREFERENCE_CATEGORY_GRAPHS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_GRAPHS));
this.findPreference(PREFERENCE_CATEGORY_LOGGING)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LOGGING));
this.findPreference(PREFERENCE_CATEGORY_ADDRESSBOOK)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADDRESSBOOK));
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
}
private void migrateOldSettings() {
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
try {
prefs.getInt("i2np.bandwidth.inboundKBytesPerSecond", 0);
} catch (ClassCastException e) {
// Migrate pre-0.9.25 settings
SharedPreferences.Editor editor = prefs.edit();
editor.remove("i2np.bandwidth.inboundKBytesPerSecond");
editor.putInt("i2np.bandwidth.inboundKBytesPerSecond", Integer.parseInt(
prefs.getString("i2np.bandwidth.inboundKBytesPerSecond", "100")));
editor.remove("i2np.bandwidth.outboundKBytesPerSecond");
editor.putInt("i2np.bandwidth.outboundKBytesPerSecond", Integer.parseInt(
prefs.getString("i2np.bandwidth.outboundKBytesPerSecond", "100")));
editor.remove("i2np.ntcp.maxConnections");
editor.putInt("i2np.ntcp.maxConnections", Integer.parseInt(
prefs.getString("i2np.ntcp.maxConnections", "32")));
editor.remove("i2np.udp.maxConnections");
editor.putInt("i2np.udp.maxConnections", Integer.parseInt(
prefs.getString("i2np.udp.maxConnections", "32")));
editor.apply();
}
}
@Override
public void onResume() {
super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings);
}
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
private String category;
public CategoryClickListener(String category) {
this.category = category;
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (PREFERENCE_CATEGORY_ADDRESSBOOK.equals(category)) {
Intent i = new Intent(getActivity(), AddressbookSettingsActivity.class);
startActivity(i);
return true;
}
Fragment fragment = getFragmentForCategory(category);
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment, fragment)
.addToBackStack(null)
.commit();
return true;
}
}
}
@Override
protected boolean isValidFragment(String fragmentName) {
return SettingsFragment.class.getName().equals(fragmentName);
private static Fragment getFragmentForCategory(String category) {
switch (category) {
case PREFERENCE_CATEGORY_NETWORK:
return new NetworkPreferenceFragment();
case PREFERENCE_CATEGORY_GRAPHS:
return new GraphsPreferenceFragment();
case PREFERENCE_CATEGORY_LOGGING:
return new LoggingPreferenceFragment();
case PREFERENCE_CATEGORY_APPEARANCE:
return new AppearancePreferenceFragment();
case PREFERENCE_CATEGORY_ADVANCED:
return new AdvancedPreferenceFragment();
default:
throw new AssertionError();
}
}
}

View File

@ -18,4 +18,14 @@ public class AddressEntry {
public Destination getDestination() {
return mDest;
}
/**
* See item 8 from Josh Bloch's "Effective Java".
*
* @return the hashcode of this AddressEntry
*/
@Override
public int hashCode() {
return 37 * mHostName.hashCode() + mDest.hashCode();
}
}

View File

@ -1,40 +1,144 @@
package net.i2p.android.router.addressbook;
import java.util.List;
import net.i2p.android.router.R;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
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;
import net.i2p.android.router.R;
import net.i2p.android.util.AlphanumericHeaderAdapter;
public AddressEntryAdapter(Context context) {
super(context, R.layout.listitem_text);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
import java.util.List;
public void setData(List<AddressEntry> addresses) {
clear();
if (addresses != null) {
for (AddressEntry address : addresses) {
add(address);
}
public class AddressEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements
AlphanumericHeaderAdapter.SortedAdapter {
private Context mCtx;
private AddressbookFragment.OnAddressSelectedListener mListener;
private List<AddressEntry> mAddresses;
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
public static class AddressViewHolder extends RecyclerView.ViewHolder {
public TextView hostName;
public AddressViewHolder(View itemView) {
super(itemView);
hostName = (TextView) itemView.findViewById(R.id.host_name);
}
}
public AddressEntryAdapter(Context context,
AddressbookFragment.OnAddressSelectedListener listener) {
super();
mCtx = context;
mListener = listener;
setHasStableIds(true);
}
public void setAddresses(List<AddressEntry> addresses) {
mAddresses = addresses;
notifyDataSetChanged();
}
public AddressEntry getAddress(int position) {
if (mAddresses == null || mAddresses.isEmpty() ||
position < 0 || position >= mAddresses.size())
return null;
return mAddresses.get(position);
}
@NonNull
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_text, parent, false);
AddressEntry address = getItem(position);
public String getSortString(int position) {
AddressEntry address = getAddress(position);
if (address == null)
return "";
TextView text = (TextView) v.findViewById(R.id.text);
text.setText(address.getHostName());
return address.getHostName();
}
return v;
@Override
public int getItemViewType(int position) {
if (mAddresses == null)
return R.string.router_not_running;
else if (mAddresses.isEmpty())
return R.layout.listitem_empty;
else
return R.layout.listitem_address;
}
// Create new views (invoked by the layout manager)
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int vt = viewType;
if (viewType == R.string.router_not_running)
vt = R.layout.listitem_empty;
View v = LayoutInflater.from(parent.getContext())
.inflate(vt, parent, false);
switch (viewType) {
case R.layout.listitem_address:
return new AddressViewHolder(v);
default:
return new SimpleViewHolder(v);
}
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case R.string.router_not_running:
((TextView) holder.itemView).setText(
mCtx.getString(R.string.router_not_running));
break;
case R.layout.listitem_empty:
((TextView) holder.itemView).setText(
mCtx.getString(R.string.addressbook_is_empty));
break;
case R.layout.listitem_address:
final AddressEntry address = getAddress(position);
AddressViewHolder avh = (AddressViewHolder) holder;
avh.hostName.setText(address.getHostName());
avh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onAddressSelected(address.getHostName());
}
});
break;
default:
break;
}
}
// Return the size of the dataset (invoked by the layout manager)
@Override
public int getItemCount() {
if (mAddresses == null || mAddresses.isEmpty())
return 1;
return mAddresses.size();
}
public long getItemId(int position) {
AddressEntry address = getAddress(position);
if (address == null)
return Integer.MAX_VALUE;
return address.hashCode();
}
}

View File

@ -3,10 +3,10 @@ package net.i2p.android.router.addressbook;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.android.router.util.Util;
import net.i2p.client.naming.NamingService;
import net.i2p.client.naming.NamingServiceListener;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
@ -16,23 +16,21 @@ import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
private I2PFragmentBase.RouterContextProvider mRContextProvider;
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> implements
NamingServiceListener {
private String mBook;
private String mFilter;
private List<AddressEntry> mData;
public AddressEntryLoader(Context context, I2PFragmentBase.RouterContextProvider rContextProvider,
String book, String filter) {
public AddressEntryLoader(Context context, String book, String filter) {
super(context);
mRContextProvider = rContextProvider;
mBook = book;
mFilter = filter;
}
@Override
public List<AddressEntry> loadInBackground() {
RouterContext routerContext = mRContextProvider.getRouterContext();
RouterContext routerContext = Util.getRouterContext();
if (routerContext == null)
return null;
@ -40,8 +38,8 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
NamingService ns = NamingServiceUtil.getNamingService(routerContext, 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>();
List<AddressEntry> ret = new ArrayList<>();
Map<String, Destination> names = new TreeMap<>();
Properties searchProps = new Properties();
// Needed for HostsTxtNamingService
@ -89,6 +87,13 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
deliverResult(mData);
}
// Begin monitoring the underlying data source.
RouterContext routerContext = Util.getRouterContext();
if (routerContext != null) {
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
ns.registerListener(this);
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
@ -119,6 +124,13 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
releaseResources(mData);
mData = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
RouterContext routerContext = Util.getRouterContext();
if (routerContext != null) {
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
ns.unregisterListener(this);
}
}
@Override
@ -136,4 +148,26 @@ public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
// would close it in this method. All resources associated with the Loader
// should be released here.
}
// NamingServiceListener
@Override
public void configurationChanged(NamingService ns) {
onContentChanged();
}
@Override
public void entryAdded(NamingService ns, String hostname, Destination dest, Properties options) {
onContentChanged();
}
@Override
public void entryChanged(NamingService ns, String hostname, Destination dest, Properties options) {
onContentChanged();
}
@Override
public void entryRemoved(NamingService ns, String hostname) {
onContentChanged();
}
}

View File

@ -1,140 +0,0 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
public class AddressbookActivity extends I2PActivityBase
implements AddressbookFragment.OnAddressSelectedListener,
SearchView.OnQueryTextListener {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private static final String SELECTED_PAGE = "selected_page";
private static final int PAGE_ROUTER = 0;
private Spinner mSpinner;
@Override
protected boolean canUseTwoPanes() {
return false;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
R.array.addressbook_pages, android.R.layout.simple_spinner_dropdown_item));
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selectPage(i);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState != null) {
int selected = savedInstanceState.getInt(SELECTED_PAGE);
mSpinner.setSelection(selected);
} else
selectPage(PAGE_ROUTER);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
}
private void selectPage(int page) {
AddressbookFragment f = new AddressbookFragment();
Bundle args = new Bundle();
args.putString(AddressbookFragment.BOOK_NAME,
page == PAGE_ROUTER ?
AddressbookFragment.ROUTER_BOOK :
AddressbookFragment.PRIVATE_BOOK);
f.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f).commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_addressbook_actions, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setOnQueryTextListener(this);
return super.onCreateOptionsMenu(menu);
}
// AddressbookFragment.OnAddressSelectedListener
public void onAddressSelected(CharSequence host) {
if (Intent.ACTION_PICK.equals(getIntent().getAction())) {
Intent result = new Intent();
result.setData(Uri.parse("http://" + host));
setResult(Activity.RESULT_OK, result);
finish();
} else {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://" + host));
startActivity(i);
}
}
// SearchView.OnQueryTextListener
public boolean onQueryTextChange(String newText) {
filterAddresses(newText);
return true;
}
public boolean onQueryTextSubmit(String query) {
filterAddresses(query);
return true;
}
private void filterAddresses(String query) {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (f instanceof AddressbookFragment) {
AddressbookFragment af = (AddressbookFragment) f;
af.filterAddresses(query);
}
}
}

View File

@ -1,12 +1,14 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
@ -19,6 +21,7 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
@ -29,7 +32,7 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
setResult(Activity.RESULT_OK, result);
result.putExtra(AddressbookFragment.ADD_WIZARD_DATA, mWizardModel.save());
result.putExtra(AddressbookContainer.ADD_WIZARD_DATA, mWizardModel.save());
dialog.dismiss();
finish();
}

View File

@ -0,0 +1,253 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.viewpagerindicator.TitlePageIndicator;
import net.i2p.android.router.R;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.android.router.util.Util;
import net.i2p.client.naming.NamingService;
public class AddressbookContainer extends Fragment
implements AddressbookFragment.OnAddressSelectedListener,
SearchView.OnQueryTextListener {
public static final int ADD_WIZARD_REQUEST = 1;
public static final String ADD_WIZARD_DATA = "add_wizard_data";
/**
* Whether or not the container is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
ViewPager mViewPager;
FragmentPagerAdapter mFragPagerAdapter;
private static final String FRAGMENT_ROUTER = "router_fragment";
private static final String FRAGMENT_PRIVATE = "private_fragment";
private static final int FRAGMENT_ID_ROUTER = 0;
private static final int FRAGMENT_ID_PRIVATE = 1;
AddressbookFragment mRouterFrag;
AddressbookFragment mPrivateFrag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.container_addressbook, container, false);
if (v.findViewById(R.id.right_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState != null) {
mRouterFrag = (AddressbookFragment) getChildFragmentManager().getFragment(
savedInstanceState, FRAGMENT_ROUTER);
mPrivateFrag = (AddressbookFragment) getChildFragmentManager().getFragment(
savedInstanceState, FRAGMENT_PRIVATE);
} else if (mTwoPane) {
// TODO if these were instantiated in the background, wouldn't savedInstanceState != null?
mRouterFrag = (AddressbookFragment) getChildFragmentManager().findFragmentById(R.id.left_fragment);
mPrivateFrag = (AddressbookFragment) getChildFragmentManager().findFragmentById(R.id.right_fragment);
// Set up the two pages
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
if (mRouterFrag == null) {
mRouterFrag = AddressbookFragment.newInstance(AddressbookFragment.ROUTER_BOOK);
ft.add(R.id.left_fragment, mRouterFrag);
}
if (mPrivateFrag == null) {
mPrivateFrag = AddressbookFragment.newInstance(AddressbookFragment.PRIVATE_BOOK);
ft.add(R.id.right_fragment, mPrivateFrag);
}
ft.commit();
}
if (!mTwoPane) {
mViewPager = (ViewPager) v.findViewById(R.id.pager);
TitlePageIndicator pageIndicator = (TitlePageIndicator) v.findViewById(R.id.page_indicator);
mFragPagerAdapter = new AddressbookPagerAdapter(getActivity(), getChildFragmentManager());
mViewPager.setAdapter(mFragPagerAdapter);
pageIndicator.setViewPager(mViewPager);
}
return v;
}
public class AddressbookPagerAdapter extends FragmentPagerAdapter {
private static final int NUM_ITEMS = 2;
private Context mContext;
public AddressbookPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case FRAGMENT_ID_ROUTER:
return (mRouterFrag = AddressbookFragment.newInstance(AddressbookFragment.ROUTER_BOOK));
case FRAGMENT_ID_PRIVATE:
return (mPrivateFrag = AddressbookFragment.newInstance(AddressbookFragment.PRIVATE_BOOK));
default:
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case FRAGMENT_ID_ROUTER:
return mContext.getString(R.string.label_router);
case FRAGMENT_ID_PRIVATE:
return mContext.getString(R.string.label_private);
default:
return null;
}
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.container_addressbook_actions, menu);
Activity activity = getActivity();
if (activity != null) {
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
searchView.setOnQueryTextListener(this);
}
}
@Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
setChildMenuVisibility(mRouterFrag, FRAGMENT_ID_ROUTER, menuVisible);
setChildMenuVisibility(mPrivateFrag, FRAGMENT_ID_PRIVATE, menuVisible);
}
private void setChildMenuVisibility(Fragment fragment, int itemNumber, boolean menuVisible) {
if (fragment != null) {
if (mViewPager != null)
menuVisible = menuVisible && mViewPager.getCurrentItem() == itemNumber;
fragment.setMenuVisibility(menuVisible);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
setChildUserVisibleHint(mRouterFrag, FRAGMENT_ID_ROUTER, isVisibleToUser);
setChildUserVisibleHint(mPrivateFrag, FRAGMENT_ID_PRIVATE, isVisibleToUser);
}
private void setChildUserVisibleHint(Fragment fragment, int itemNumber, boolean isVisibleToUser) {
if (fragment != null) {
if (mViewPager != null)
isVisibleToUser = isVisibleToUser && mViewPager.getCurrentItem() == itemNumber;
fragment.setUserVisibleHint(isVisibleToUser);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Since the pager fragments don't have known tags or IDs, the only way to persist the
// reference is to use putFragment/getFragment. Remember, we're not persisting the exact
// Fragment instance. This mechanism simply gives us a way to persist access to the
// 'current' fragment instance for the given fragment (which changes across orientation
// changes).
//
// The outcome of all this is that the "Refresh" menu button refreshes the stream across
// orientation changes.
if (mRouterFrag != null)
getChildFragmentManager().putFragment(outState, FRAGMENT_ROUTER, mRouterFrag);
if (mPrivateFrag != null)
getChildFragmentManager().putFragment(outState, FRAGMENT_PRIVATE, mPrivateFrag);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ADD_WIZARD_REQUEST &&
resultCode == Activity.RESULT_OK) {
// Save the new entry
Bundle entryData = data.getExtras().getBundle(ADD_WIZARD_DATA);
NamingService ns = NamingServiceUtil.getNamingService(Util.getRouterContext(),
AddressbookFragment.PRIVATE_BOOK);
NamingServiceUtil.addFromWizard(getActivity(), ns, entryData, false);
// The loader will be notified by the NamingService
}
}
// AddressbookFragment.OnAddressSelectedListener
public void onAddressSelected(CharSequence host) {
if (Intent.ACTION_PICK.equals(getActivity().getIntent().getAction())) {
Intent result = new Intent();
result.setData(Uri.parse("http://" + host));
getActivity().setResult(Activity.RESULT_OK, result);
getActivity().finish();
} else {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://" + host));
startActivity(i);
}
}
// SearchView.OnQueryTextListener
public boolean onQueryTextChange(String newText) {
filterAddresses(newText);
return true;
}
public boolean onQueryTextSubmit(String query) {
filterAddresses(query);
return true;
}
private void filterAddresses(String query) {
if (mRouterFrag != null)
mRouterFrag.filterAddresses(query);
if (mPrivateFrag != null)
mPrivateFrag.filterAddresses(query);
}
}

View File

@ -1,11 +1,17 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
@ -13,51 +19,56 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.eowise.recyclerview.stickyheaders.StickyHeadersBuilder;
import com.eowise.recyclerview.stickyheaders.StickyHeadersItemDecoration;
import com.pnikosis.materialishprogress.ProgressWheel;
import net.i2p.addressbook.Daemon;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.android.router.R;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.client.naming.NamingService;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.AlphanumericHeaderAdapter;
import net.i2p.android.util.FragmentUtils;
import net.i2p.android.widget.DividerItemDecoration;
import net.i2p.android.widget.LoadingRecyclerView;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.List;
public class AddressbookFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
public class AddressbookFragment extends Fragment implements
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 LoadingRecyclerView mRecyclerView;
private AddressEntryAdapter mAdapter;
private String mBook;
private String mCurFilter;
private ImageButton mAddToAddressbook;
// Set in onActivityResult()
private Intent mAddWizardData;
// Container Activity must implement this interface
public interface OnAddressSelectedListener {
public void onAddressSelected(CharSequence host);
void onAddressSelected(CharSequence host);
}
public static AddressbookFragment newInstance(String book) {
AddressbookFragment f = new AddressbookFragment();
Bundle args = new Bundle();
args.putString(AddressbookFragment.BOOK_NAME, book);
f.setArguments(args);
return f;
}
@Override
@ -66,21 +77,9 @@ public class AddressbookFragment extends ListFragment implements
// 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");
}
mCallback = FragmentUtils.getParent(this, OnAddressSelectedListener.class);
if (mCallback == null)
throw new ClassCastException("Parent must implement OnAddressSelectedListener");
}
@ -92,19 +91,19 @@ public class AddressbookFragment extends ListFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Create the list fragment's content view by calling the super method
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
listContainer.addView(listFragmentView);
mRecyclerView = (LoadingRecyclerView) v.findViewById(R.id.list);
View empty = v.findViewById(R.id.empty);
ProgressWheel loading = (ProgressWheel) v.findViewById(R.id.loading);
mRecyclerView.setLoadingView(empty, loading);
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
startActivityForResult(wi, ADD_WIZARD_REQUEST);
getParentFragment().startActivityForResult(wi, AddressbookContainer.ADD_WIZARD_REQUEST);
}
});
@ -114,75 +113,102 @@ public class AddressbookFragment extends ListFragment implements
@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);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
TextView v = new TextView(getActivity());
v.setTag("addressbook_header");
getListView().addHeaderView(v);
// use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
setListAdapter(mAdapter);
// Set the adapter for the list view
mAdapter = new AddressEntryAdapter(getActivity(), mCallback);
mRecyclerView.setAdapter(mAdapter);
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
else
setEmptyText(getResources().getString(
R.string.router_not_running));
// Build item decoration and add it to the RecyclerView
StickyHeadersItemDecoration decoration = new StickyHeadersBuilder()
.setAdapter(mAdapter)
.setRecyclerView(mRecyclerView)
.setStickyHeadersAdapter(new AlphanumericHeaderAdapter(mAdapter))
.build();
mRecyclerView.addItemDecoration(decoration);
// Initialize the adapter in case the RouterService has not been created
if (Util.getRouterContext() == null)
mAdapter.setAddresses(null);
}
public void onRouterConnectionReady() {
// Show actions
if (mSearchAddressbook != null)
mSearchAddressbook.setVisible(true);
if (mAddToAddressbook != null && mAddToAddressbook.getVisibility() != View.VISIBLE)
mAddToAddressbook.setVisibility(View.VISIBLE);
@Override
public void onStart() {
super.onStart();
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);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
}
private State lastRouterState = null;
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (lastRouterState == null || lastRouterState != state) {
updateState(state);
lastRouterState = state;
}
} else {
setEmptyText("No hosts in address book " + mBook);
}
};
setListShown(false);
getLoaderManager().initLoader(PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
public void updateState(State state) {
int loaderId = PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID;
if (state == State.STOPPING || state == State.STOPPED ||
state == State.MANUAL_STOPPING ||
state == State.MANUAL_STOPPED ||
state == State.MANUAL_QUITTING ||
state == State.MANUAL_QUITTED)
getLoaderManager().destroyLoader(loaderId);
else {
mRecyclerView.setLoading(true);
getLoaderManager().initLoader(loaderId, null, this);
}
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
CharSequence host = ((TextView) view).getText();
mCallback.onAddressSelected(host);
public void onResume() {
super.onResume();
// Triggers loader init via updateState() if the router is running
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
}
private MenuItem mSearchAddressbook;
@Override
public void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
// Hide until needed
if (getRouterContext() == null) {
mSearchAddressbook.setVisible(false);
if (mAddToAddressbook != null)
mAddToAddressbook.setVisibility(View.GONE);
}
RouterContext rCtx = Util.getRouterContext();
if (mAddToAddressbook != null)
mAddToAddressbook.setVisibility(rCtx == null ? View.GONE : View.VISIBLE);
// Only show "Reload subscriptions" for router addressbook
menu.findItem(R.id.action_reload_subscriptions).setVisible(
rCtx != null && !PRIVATE_BOOK.equals(mBook));
// Only allow adding to private book
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
@ -201,88 +227,41 @@ public class AddressbookFragment extends ListFragment implements
Toast.makeText(getActivity(), "Reloading subscriptions...",
Toast.LENGTH_SHORT).show();
return true;
case R.id.action_addressbook_settings:
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
startActivity(si);
return true;
case R.id.action_addressbook_help:
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ADD_WIZARD_REQUEST &&
resultCode == Activity.RESULT_OK &&
PRIVATE_BOOK.equals(mBook)) {
mAddWizardData = data;
}
}
public void filterAddresses(String query) {
mCurFilter = !TextUtils.isEmpty(query) ? query : null;
if (getRouterContext() != null && mAdapter != null) {
setListShown(false);
if (Util.getRouterContext() != null && mAdapter != null) {
mRecyclerView.setLoading(true);
getLoaderManager().restartLoader(PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
}
}
// Duplicated from I2PFragmentBase because this extends ListFragment
private RouterContext getRouterContext() {
return mRouterContextProvider.getRouterContext();
}
// I2PFragmentBase.RouterContextUser
public void onRouterBind() {
if (mOnActivityCreated)
onRouterConnectionReady();
}
// LoaderManager.LoaderCallbacks<List<AddressEntry>>
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
return new AddressEntryLoader(getActivity(),
mRouterContextProvider, mBook, mCurFilter);
return new AddressEntryLoader(getActivity(), mBook, mCurFilter);
}
public void onLoadFinished(Loader<List<AddressEntry>> loader,
List<AddressEntry> data) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
if (data == null)
setEmptyText(getResources().getString(
R.string.router_not_running));
else {
mAdapter.setData(data);
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
if (mCurFilter != null)
v.setText(getActivity().getResources().getString(
R.string.addressbook_search_header,
data.size()));
else
v.setText("");
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
mAdapter.setAddresses(data);
}
}
public void onLoaderReset(Loader<List<AddressEntry>> loader) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
mAdapter.setData(null);
if (Util.getRouterContext() == null)
mAdapter.setAddresses(null);
else
mAdapter.setAddresses(new ArrayList<AddressEntry>());
}
}
}

View File

@ -2,7 +2,7 @@ package net.i2p.android.router.addressbook;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
@ -10,21 +10,25 @@ import android.widget.EditText;
import android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.util.LocaleManager;
import net.i2p.util.FileUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class AddressbookSettingsActivity extends ActionBarActivity {
public class AddressbookSettingsActivity extends AppCompatActivity {
private EditText text_content_subscriptions;
private Button btn_save_subscriptions;
private String filename = "/addressbook/subscriptions.txt";
private File i2pDir;
private final LocaleManager localeManager = new LocaleManager();
@Override
public void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_addressbook_settings);
@ -84,4 +88,10 @@ public class AddressbookSettingsActivity extends ActionBarActivity {
if (out != null) try {out.close(); } catch (IOException ioe) {}
}
}
@Override
public void onResume() {
super.onResume();
localeManager.onResume(this);
}
}

View File

@ -1,21 +1,24 @@
package net.i2p.android.router.dialog;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import net.i2p.android.router.util.Util;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import net.i2p.android.router.util.Util;
public class AboutDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
@ -37,6 +40,12 @@ public class AboutDialog extends DialogFragment {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.menu_about)
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
}
})
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {

View File

@ -1,42 +0,0 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
public class ConfigureBrowserDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_browser_title)
.setMessage(R.string.configure_browser_for_i2p)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
});
return b.create();
}
}

View File

@ -1,18 +1,27 @@
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.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import java.util.List;
public class FirstStartDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
@ -20,12 +29,30 @@ public class FirstStartDialog extends DialogFragment {
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://");
// Find all installed browsers that listen for "irc://"
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("irc://127.0.0.1:6668/i2p"));
final PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> installedIrcClients = pm.queryIntentActivities(intent, 0);
// Only linkify "irc://" if we have an app that can handle them.
// Otherwise, the app crashes with an un-catchable ActivityNotFoundException
// if the user clicks one of them.
if (installedIrcClients.size() > 0) {
tv = (TextView) view.findViewById(R.id.url_irc_i2p);
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
}
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.first_start_title)
.setView(view);
.setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
}
});
return b.create();
}
}

View File

@ -1,11 +1,14 @@
package net.i2p.android.router.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import net.i2p.android.router.R;
@ -17,30 +20,38 @@ import java.io.InputStream;
import java.io.UnsupportedEncodingException;
/**
* Display a raw text resource.
* The resource ID must be passed as an extra in the intent.
* Display a raw text resource.
* The resource ID must be passed as an extra in the intent.
*/
public class TextResourceDialog extends DialogFragment {
public static final String TEXT_DIALOG_TITLE = "text_title";
public final static String TEXT_RESOURCE_ID = "text_resource_id";
@NonNull
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = LayoutInflater.from(getActivity());
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, null, false);
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
String title = getArguments().getString(TEXT_DIALOG_TITLE);
if (title != null)
getDialog().setTitle(title);
b.setTitle(title);
int id = getArguments().getInt(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
if (id == R.raw.releasenotes_txt)
tv.setText("Release Notes for Release " + Util.getOurVersion(getActivity()) + "\n\n" +
getResourceAsString(id));
getResourceAsString(id));
else
tv.setText(getResourceAsString(id));
return v;
b.setView(v)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
return b.create();
}
private String getResourceAsString(int id) {
@ -49,17 +60,18 @@ public class TextResourceDialog extends DialogFragment {
byte buf[] = new byte[1024];
try {
in = getResources().openRawResource(id);
int read;
while ( (read = in.read(buf)) != -1)
while ((read = in.read(buf)) != -1)
out.write(buf, 0, read);
} catch (IOException ioe) {
System.err.println("resource error " + ioe);
} catch (Resources.NotFoundException nfe) {
System.err.println("resource error " + nfe);
} catch (IOException | Resources.NotFoundException re) {
System.err.println("resource error " + re);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (in != null) try {
in.close();
} catch (IOException ioe) {
}
}
try {
return out.toString("UTF-8");

View File

@ -1,24 +1,27 @@
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.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.router.MainFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
public class VersionDialog extends DialogFragment {
public static final String DIALOG_TYPE = "dialog_type";
public static final int DIALOG_NEW_INSTALL = 0;
public static final int DIALOG_NEW_VERSION = 1;
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
final String currentVersion = Util.getOurVersion(getActivity());
Dialog rv = null;
Dialog rv;
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
int id = getArguments().getInt(DIALOG_TYPE);
switch(id) {
@ -53,6 +56,7 @@ public class VersionDialog extends DialogFragment {
break;
case DIALOG_NEW_VERSION:
default:
b.setMessage(getResources().getString(R.string.welcome_new_version) +
" " + currentVersion)
.setCancelable(true)

View File

@ -3,6 +3,7 @@ package net.i2p.android.router.log;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@ -11,7 +12,7 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
@ -28,21 +29,20 @@ public class LogActivity extends I2PActivityBase implements
private String[] mLevels;
private Spinner mSpinner;
@Override
protected boolean canUseTwoPanes() {
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multipane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mLevels = getResources().getStringArray(R.array.log_level_list);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the

View File

@ -1,7 +1,9 @@
package net.i2p.android.router.log;
import android.os.Bundle;
import net.i2p.android.router.I2PActivityBase;
import android.support.v7.widget.Toolbar;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.router.R;
public class LogDetailActivity extends I2PActivityBase {
@ -10,7 +12,11 @@ public class LogDetailActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
setContentView(R.layout.activity_onepane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
String entry = getIntent().getStringExtra(LogDetailFragment.LOG_ENTRY);

View File

@ -1,8 +1,6 @@
package net.i2p.android.router.log;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@ -16,12 +14,15 @@ import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
public class LogDetailFragment extends I2PFragmentBase {
public static final String LOG_ENTRY = "log_entry";
private String mEntry;
public static LogDetailFragment newInstance (String entry) {
public static LogDetailFragment newInstance(String entry) {
LogDetailFragment f = new LogDetailFragment();
Bundle args = new Bundle();
args.putString(LOG_ENTRY, entry);
@ -37,7 +38,7 @@ public class LogDetailFragment extends I2PFragmentBase {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_log_entry, container, false);
mEntry = getArguments().getString(LOG_ENTRY);
@ -58,15 +59,10 @@ public class LogDetailFragment extends I2PFragmentBase {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_copy_logs:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mEntry);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
getString(R.string.i2p_android_logs), mEntry);
clipboard.setPrimaryClip(clip);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
copyToClipbardLegacy();
else
copyToClipboardHoneycomb();
Toast.makeText(getActivity(), R.string.logs_copied_to_clipboard, Toast.LENGTH_SHORT).show();
return true;
@ -74,4 +70,17 @@ public class LogDetailFragment extends I2PFragmentBase {
return super.onOptionsItemSelected(item);
}
}
private void copyToClipbardLegacy() {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mEntry);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void copyToClipboardHoneycomb() {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
getString(R.string.i2p_android_logs), mEntry);
clipboard.setPrimaryClip(clip);
}
}

View File

@ -15,14 +15,16 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import java.util.ArrayList;
import java.util.List;
public class LogFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<String>> {
public static final String LOG_LEVEL = "log_level";
public static final String LOG_LEVEL_ERROR = "ERROR";
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
@ -33,7 +35,7 @@ public class LogFragment extends ListFragment implements
private static final int LEVEL_ALL = 2;
OnEntrySelectedListener mEntrySelectedCallback;
private final List<String> mLogEntries = new ArrayList<String>();
private final List<String> mLogEntries = new ArrayList<>();
private LogAdapter mAdapter;
private TextView mHeaderView;
private String mLogLevel;
@ -47,7 +49,7 @@ public class LogFragment extends ListFragment implements
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(String entry);
void onEntrySelected(String entry);
}
public static LogFragment newInstance(String level) {
@ -112,11 +114,11 @@ public class LogFragment extends ListFragment implements
I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null) {
setEmptyText("ERROR".equals(mLogLevel) ?
"No error messages" : "No messages");
setEmptyText(getString(LOG_LEVEL_ERROR.equals(mLogLevel) ?
R.string.no_error_messages : R.string.no_messages));
setListShown(false);
getLoaderManager().initLoader("ERROR".equals(mLogLevel) ?
getLoaderManager().initLoader(LOG_LEVEL_ERROR.equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL, null, this);
} else
setEmptyText(getResources().getString(
@ -162,7 +164,7 @@ public class LogFragment extends ListFragment implements
}
}
boolean isError = "ERROR".equals(mLogLevel);
boolean isError = LOG_LEVEL_ERROR.equals(mLogLevel);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(logText);
@ -204,20 +206,12 @@ public class LogFragment extends ListFragment implements
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";
private static String getHeader(Context ctx, int sz, boolean errorsOnly) {
if (sz > 0)
return ctx.getResources().getQuantityString(errorsOnly ?
R.plurals.log_error_messages : R.plurals.log_messages, sz, sz);
else
return ctx.getString(errorsOnly ? R.string.no_error_messages : R.string.no_messages);
}
// LoaderManager.LoaderCallbacks<List<String>>
@ -229,14 +223,14 @@ public class LogFragment extends ListFragment implements
public void onLoadFinished(Loader<List<String>> loader,
List<String> data) {
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
if (loader.getId() == (LOG_LEVEL_ERROR.equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL)) {
synchronized (mLogEntries) {
mLogEntries.clear();
mLogEntries.addAll(data);
}
mAdapter.setData(data);
String header = getHeader(data.size(), ("ERROR".equals(mLogLevel)));
String header = getHeader(getActivity(), data.size(), (LOG_LEVEL_ERROR.equals(mLogLevel)));
mHeaderView.setText(header);
if (isResumed()) {
@ -248,7 +242,7 @@ public class LogFragment extends ListFragment implements
}
public void onLoaderReset(Loader<List<String>> loader) {
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
if (loader.getId() == (LOG_LEVEL_ERROR.equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL)) {
mAdapter.setData(null);
}

View File

@ -1,13 +1,13 @@
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;
import net.i2p.I2PAppContext;
import java.util.Collections;
import java.util.List;
public class LogLoader extends AsyncTaskLoader<List<String>> {
private I2PAppContext mCtx;
private String mLogLevel;
@ -24,7 +24,7 @@ public class LogLoader extends AsyncTaskLoader<List<String>> {
@Override
public List<String> loadInBackground() {
List<String> msgs;
if ("ERROR".equals(mLogLevel)) {
if (LogFragment.LOG_LEVEL_ERROR.equals(mLogLevel)) {
msgs = mCtx.logManager().getBuffer().getMostRecentCriticalMessages();
} else {
msgs = mCtx.logManager().getBuffer().getMostRecentMessages();

View File

@ -3,12 +3,13 @@ package net.i2p.android.router.netdb;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.data.Hash;
@ -26,14 +27,14 @@ public class NetDbActivity extends I2PActivityBase implements
private Spinner mSpinner;
@Override
protected boolean canUseTwoPanes() {
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multipane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);

View File

@ -1,12 +1,14 @@
package net.i2p.android.router.netdb;
import net.i2p.android.router.I2PActivityBase;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import net.i2p.android.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 {
@ -15,7 +17,10 @@ public class NetDbDetailActivity extends I2PActivityBase implements
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
setContentView(R.layout.activity_onepane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
if (savedInstanceState == null) {
boolean isRI = getIntent().getBooleanExtra(NetDbDetailFragment.IS_RI, true);

View File

@ -1,26 +1,26 @@
package net.i2p.android.router.netdb;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.util.Util;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.router.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) {
public NetDbEntryLoader(Context context, boolean routers) {
super(context);
mRContext = rContext;
mRouters = routers;
}
@ -31,6 +31,13 @@ public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
}
private class LeaseSetComparator implements Comparator<LeaseSet> {
private RouterContext mRContext;
public LeaseSetComparator(RouterContext rContext) {
super();
mRContext = rContext;
}
public int compare(LeaseSet l, LeaseSet r) {
Destination dl = l.getDestination();
Destination dr = r.getDestination();
@ -44,20 +51,21 @@ public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
@Override
public List<NetDbEntry> loadInBackground() {
List<NetDbEntry> ret = new ArrayList<NetDbEntry>();
if (mRContext.netDb().isInitialized()) {
List<NetDbEntry> ret = new ArrayList<>();
RouterContext routerContext = Util.getRouterContext();
if (routerContext != null && routerContext.netDb().isInitialized()) {
if (mRouters) {
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
routers.addAll(mRContext.netDb().getRouters());
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
routers.addAll(routerContext.netDb().getRouters());
for (RouterInfo ri : routers) {
NetDbEntry entry = NetDbEntry.fromRouterInfo(mRContext, ri);
NetDbEntry entry = NetDbEntry.fromRouterInfo(routerContext, ri);
ret.add(entry);
}
} else {
Set<LeaseSet> leases = new TreeSet<LeaseSet>(new LeaseSetComparator());
leases.addAll(mRContext.netDb().getLeases());
Set<LeaseSet> leases = new TreeSet<>(new LeaseSetComparator(routerContext));
leases.addAll(routerContext.netDb().getLeases());
for (LeaseSet ls : leases) {
NetDbEntry entry = NetDbEntry.fromLeaseSet(mRContext, ls);
NetDbEntry entry = NetDbEntry.fromLeaseSet(routerContext, ls);
ret.add(entry);
}
}

View File

@ -11,16 +11,13 @@ 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.android.router.util.Util;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import java.util.List;
public class NetDbListFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
public class NetDbListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<NetDbEntry>> {
public static final String SHOW_ROUTERS = "show_routers";
@ -32,8 +29,6 @@ public class NetDbListFragment extends ListFragment implements
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
private boolean mOnActivityCreated;
private RouterContextProvider mRouterContextProvider;
private OnEntrySelectedListener mEntrySelectedCallback;
private NetDbEntryAdapter mAdapter;
private boolean mRouters;
@ -45,22 +40,13 @@ public class NetDbListFragment extends ListFragment implements
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(boolean isRouterInfo, Hash entryHash);
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 {
@ -104,22 +90,18 @@ public class NetDbListFragment extends ListFragment implements
setListAdapter(mAdapter);
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
else
if (Util.getRouterContext() == null)
setEmptyText(getResources().getString(
R.string.router_not_running));
}
public void onRouterConnectionReady() {
setEmptyText(getResources().getString((mRouters ?
R.string.netdb_routers_empty :
else {
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);
setListShown(false);
getLoaderManager().initLoader(mRouters ? ROUTER_LOADER_ID
: LEASESET_LOADER_ID, null, this);
}
}
@Override
@ -149,7 +131,7 @@ public class NetDbListFragment extends ListFragment implements
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_refresh:
if (getRouterContext() != null) {
if (Util.getRouterContext() != null) {
setListShown(false);
getLoaderManager().restartLoader(mRouters ? ROUTER_LOADER_ID
: LEASESET_LOADER_ID, null, this);
@ -178,23 +160,10 @@ public class NetDbListFragment extends ListFragment implements
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);
return new NetDbEntryLoader(getActivity(), mRouters);
}
public void onLoadFinished(Loader<List<NetDbEntry>> loader,

View File

@ -1,19 +1,21 @@
package net.i2p.android.router.netdb;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.R;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.ObjectCounter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.ObjectCounter;
import 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;
@ -31,16 +33,16 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
@Override
public List<ObjectCounter<String>> loadInBackground() {
List<ObjectCounter<String>> ret = new ArrayList<ObjectCounter<String>>();
List<ObjectCounter<String>> ret = new ArrayList<>();
ObjectCounter<String> versions = new ObjectCounter<String>();
ObjectCounter<String> countries = new ObjectCounter<String>();
ObjectCounter<String> transports = new ObjectCounter<String>();
ObjectCounter<String> versions = new ObjectCounter<>();
ObjectCounter<String> countries = new ObjectCounter<>();
ObjectCounter<String> transports = new ObjectCounter<>();
if (mRContext != null && mRContext.netDb() != null && mRContext.netDb().isInitialized()) {
Hash us = mRContext.routerHash();
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
routers.addAll(mRContext.netDb().getRouters());
for (RouterInfo ri : routers) {
Hash key = ri.getHash();
@ -58,7 +60,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
}
ret.add(versions);
ret.add(countries);
//ret.add(countries);
ret.add(transports);
return ret;
@ -68,14 +70,28 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
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" };
private static final int[] TNAMES = {
R.string.tname_0,
R.string.tname_1,
R.string.tname_2,
0,
R.string.tname_4,
R.string.tname_5,
R.string.tname_6,
0,
0,
R.string.tname_9,
R.string.tname_10,
R.string.tname_11,
R.string.tname_12,
R.string.tname_13,
R.string.tname_14,
R.string.tname_15,
};
/**
* what transport types
*/
private static String classifyTransports(RouterInfo info) {
private String classifyTransports(RouterInfo info) {
int rv = 0;
for (RouterAddress addr : info.getAddresses()) {
String style = addr.getTransportStyle();
@ -92,7 +108,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
rv |= IPV6;
}
return TNAMES[rv];
return getContext().getString(TNAMES[rv]);
}
@Override

View File

@ -1,11 +1,5 @@
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;
@ -20,6 +14,13 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
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 java.util.List;
public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>> {
private NetDbPagerAdapter mNetDbPagerAdapter;
@ -33,8 +34,7 @@ public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.parentfragment_viewpager, container, false);
return v;
return inflater.inflate(R.layout.parentfragment_viewpager, container, false);
}
@Override
@ -113,18 +113,18 @@ public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
if (mData == null)
return 0;
else
return 3;
return 2;
}
@Override
public CharSequence getPageTitle(int i) {
switch (i) {
case 1:
return "Countries";
case 2:
return "Transports";
// return getString(R.string.countries);
//case 2:
return getString(R.string.settings_label_transports);
default:
return "Versions";
return getString(R.string.versions);
}
}
}

View File

@ -1,12 +1,5 @@
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;
@ -16,6 +9,14 @@ import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.util.ObjectCounter;
import net.i2p.util.VersionComparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NetDbSummaryTableFragment extends Fragment {
private static final String CATEGORY = "category";
private static final String COUNTS = "counts";
@ -44,13 +45,13 @@ public class NetDbSummaryTableFragment extends Fragment {
mTable = (TableLayout) v.findViewById(R.id.table);
List<String> objects = new ArrayList<String>(mCounts.objects());
List<String> objects = new ArrayList<>(mCounts.objects());
if (!objects.isEmpty()) {
createTableTitle();
switch (mCategory) {
case 1:
case 2:
//case 2:
Collections.sort(objects);
break;
default:
@ -82,16 +83,16 @@ public class NetDbSummaryTableFragment extends Fragment {
switch (mCategory) {
case 1:
tl1.setText("Transports");
break;
case 2:
tl1.setText("Country");
// tl1.setText(R.string.country);
// break;
//case 2:
tl1.setText(R.string.transport);
break;
default:
tl1.setText("Version");
tl1.setText(R.string.version);
break;
}
tl2.setText("Count");
tl2.setText(R.string.count);
titleRow.addView(tl1);
titleRow.addView(tl2);

View File

@ -6,6 +6,12 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.BuildConfig;
import net.i2p.android.router.util.AppCache;
import net.i2p.android.router.util.Util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -13,10 +19,6 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.BuildConfig;
import net.i2p.android.router.util.AppCache;
import net.i2p.android.router.util.Util;
/**
* Usage: content://net.i2p.android.router/NONCE/ENCODED-SCHEME/ENCODED-AUTHORITY/ENCODED_PATH + QUERY_MARKER + ENCODED-QUERY
@ -141,7 +143,7 @@ public class CacheProvider extends ContentProvider {
*
* @param uri must contain a scheme, authority and path with nonce etc. as defined above
* @return non-null
* @throws FNFE on error
* @throws java.io.FileNotFoundException on error
*/
public static Uri getI2PUri(Uri uri) throws FileNotFoundException {
String resPath = uri.getEncodedPath();
@ -243,8 +245,7 @@ public class CacheProvider extends ContentProvider {
if (file.length() > 0) {
// this call will insert it back to us (don't set as current base)
Uri content = cache.addCacheFile(uri, false);
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return parcel;
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} else {
Util.d("CacheProvider Sucess but no data " + uri);
}
@ -276,7 +277,7 @@ public class CacheProvider extends ContentProvider {
put(uri, fileURI);
}
Boolean setAsCurrentBase = values.getAsBoolean(CURRENT_BASE);
if (setAsCurrentBase != null && setAsCurrentBase.booleanValue()) {
if (setAsCurrentBase != null && setAsCurrentBase) {
Util.d("CacheProvider set current base " + uri);
setCurrentBase(uri);
}
@ -303,7 +304,7 @@ public class CacheProvider extends ContentProvider {
private void cleanup() {
String pfx = CONTENT_URI.toString();
List<String> toDelete = new ArrayList<String>();
List<String> toDelete = new ArrayList<>();
Map<String, ?> map = _sharedPrefs.getAll();
for (Map.Entry<String, ?> e : map.entrySet()) {
String path = (String) e.getValue();

View File

@ -1,20 +1,18 @@
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;
import java.io.File;
class Init {
private final Context ctx;
private final String myDir;
private final String _ourVersion;
public Init(Context c) {
ctx = c;
myDir = c.getFilesDir().getAbsolutePath();
_ourVersion = Util.getOurVersion(c);
}
void initialize() {
@ -32,8 +30,7 @@ class Init {
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];
for (File f : files) {
Util.d("Deleting old file/dir " + f);
FileUtil.rmdir(f, false);
}

View File

@ -1,9 +1,9 @@
package net.i2p.android.router.service;
import java.io.IOException;
import android.content.Context;
import net.i2p.I2PAppContext;
import net.i2p.BOB.BOB;
import net.i2p.I2PAppContext;
import net.i2p.addressbook.DaemonThread;
import net.i2p.android.apps.NewsFetcher;
import net.i2p.android.router.util.Notifications;
@ -14,6 +14,8 @@ import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PAppThread;
import java.io.IOException;
/**
* Load the clients we want.
*
@ -35,6 +37,7 @@ import net.i2p.util.I2PAppThread;
*/
class LoadClientsJob extends JobImpl {
private Context mCtx;
private Notifications _notif;
private DaemonThread _addressbook;
private BOB _bob;
@ -43,8 +46,9 @@ class LoadClientsJob extends JobImpl {
private static final long LOAD_DELAY = 90*1000;
public LoadClientsJob(RouterContext ctx, Notifications notif) {
super(ctx);
public LoadClientsJob(Context ctx, RouterContext rCtx, Notifications notif) {
super(rCtx);
mCtx = ctx;
_notif = notif;
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
}
@ -59,7 +63,7 @@ class LoadClientsJob extends JobImpl {
t.setPriority(Thread.NORM_PRIORITY - 1);
t.start();
NewsFetcher fetcher = NewsFetcher.getInstance(getContext(), _notif);
NewsFetcher fetcher = NewsFetcher.getInstance(mCtx, getContext(), _notif);
t = new I2PAppThread(fetcher, "NewsFetcher", true);
t.start();
@ -88,8 +92,13 @@ class LoadClientsJob extends JobImpl {
public void runJob() {
Util.d("Starting i2ptunnel");
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
int sz = tcg.getControllers().size();
Util.d("i2ptunnel started " + sz + " clients");
try {
tcg.startup();
int sz = tcg.getControllers().size();
Util.d("i2ptunnel started " + sz + " clients");
} catch (IllegalArgumentException iae) {
Util.e("i2ptunnel failed to start", iae);
}
}
}

View File

@ -1,40 +1,59 @@
package net.i2p.android.router.service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import 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 android.support.v4.content.LocalBroadcastManager;
import net.i2p.android.router.R;
import net.i2p.android.router.receiver.I2PReceiver;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Notifications;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.LocaleManager;
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;
import java.lang.ref.WeakReference;
/**
* Runs the router
*/
public class RouterService extends Service {
/**
* A request to this service for the current router state. Broadcasting
* this will trigger a state notification.
*/
public static final String LOCAL_BROADCAST_REQUEST_STATE = "net.i2p.android.LOCAL_BROADCAST_REQUEST_STATE";
/**
* A notification of the current state. This is informational; the state
* has not changed.
*/
public static final String LOCAL_BROADCAST_STATE_NOTIFICATION = "net.i2p.android.LOCAL_BROADCAST_STATE_NOTIFICATION";
/**
* The state has just changed.
*/
public static final String LOCAL_BROADCAST_STATE_CHANGED = "net.i2p.android.LOCAL_BROADCAST_STATE_CHANGED";
public static final String LOCAL_BROADCAST_EXTRA_STATE = "net.i2p.android.STATE";
/**
* The locale has just changed.
*/
public static final String LOCAL_BROADCAST_LOCALE_CHANGED = "net.i2p.android.LOCAL_BROADCAST_LOCALE_CHANGED";
private LocaleManager localeManager = new LocaleManager();
private RouterContext _context;
private String _myDir;
//private String _apkPath;
@ -47,7 +66,6 @@ public class RouterService extends Service {
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";
@ -59,11 +77,10 @@ public class RouterService extends Service {
* that it can be accessed more efficiently from inner classes.
*/
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
= new RemoteCallbackList<IRouterStateCallback>();
= new RemoteCallbackList<>();
@Override
public void onCreate() {
mStartCalled = false;
State lastState = getSavedState();
setState(State.INIT);
Util.d(this + " onCreate called"
@ -83,11 +100,14 @@ public class RouterService extends Service {
_binder = new RouterBinder(this);
_handler = new Handler();
_updater = new Updater();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
lbm.registerReceiver(onStateRequested, new IntentFilter(LOCAL_BROADCAST_REQUEST_STATE));
lbm.registerReceiver(onLocaleChanged, new IntentFilter(LOCAL_BROADCAST_LOCALE_CHANGED));
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) {
} else if(lastState == State.MANUAL_QUITTING || lastState == State.GRACEFUL_SHUTDOWN) {
synchronized(_stateLock) {
setState(State.MANUAL_QUITTED);
stopSelf(); // Die.
@ -95,6 +115,23 @@ public class RouterService extends Service {
}
}
private BroadcastReceiver onStateRequested = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Broadcast the current state within this app.
Intent ni = new Intent(LOCAL_BROADCAST_STATE_NOTIFICATION);
ni.putExtra(LOCAL_BROADCAST_EXTRA_STATE, (android.os.Parcelable) _state);
LocalBroadcastManager.getInstance(RouterService.this).sendBroadcast(ni);
}
};
private BroadcastReceiver onLocaleChanged = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
localeManager.updateServiceLocale(RouterService.this);
}
};
/**
* NOT called by system if it restarts us after a crash
*/
@ -105,7 +142,6 @@ public class RouterService extends Service {
+ " 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");
@ -118,15 +154,15 @@ public class RouterService extends Service {
_receiver = new I2PReceiver(this);
if(Connectivity.isConnected(this)) {
if(restart) {
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting");
_statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_restarting);
} else {
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
_statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_starting);
}
setState(State.STARTING);
_starterThread = new Thread(new Starter());
_starterThread.start();
} else {
_statusBar.replace(StatusBar.ICON_WAITING_NETWORK, "I2P is waiting for a network connection");
_statusBar.replace(StatusBar.ICON_WAITING_NETWORK, R.string.notification_status_waiting);
setState(State.WAITING);
_handler.postDelayed(new Waiter(), 10 * 1000);
}
@ -155,7 +191,7 @@ public class RouterService extends Service {
if(_state != State.WAITING) {
return;
}
_statusBar.replace(StatusBar.ICON_STARTING, "Network connected, I2P is starting up");
_statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_starting_after_waiting);
setState(State.STARTING);
_starterThread = new Thread(new Starter());
_starterThread.start();
@ -176,160 +212,20 @@ public class RouterService extends Service {
//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!
// Launch the router!
RouterLaunch.main(null);
synchronized(_stateLock) {
if(_state != State.STARTING) {
return;
}
setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
_statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_running);
_context = Util.getRouterContext();
if (_context == null) {
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
}
_context.router().setKillVMOnEnd(false);
Job loadJob = new LoadClientsJob(_context, _notif);
Job loadJob = new LoadClientsJob(RouterService.this, _context, _notif);
_context.jobQueue().addJob(loadJob);
_context.addShutdownTask(new ShutdownHook());
_context.addFinalShutdownTask(new FinalShutdownHook());
@ -343,7 +239,7 @@ public class RouterService extends Service {
public void run() {
RouterContext ctx = _context;
if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE)) {
if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN)) {
Router router = ctx.router();
if(router.isAlive()) {
updateStatus(ctx);
@ -363,25 +259,16 @@ public class RouterService extends Service {
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");
}
double inBW = ctx.bandwidthLimiter().getReceiveBps();
double outBW = ctx.bandwidthLimiter().getSendBps();
String text =
getResources().getString(R.string.notification_status_bw,
fmt.format(inBW), fmt.format(outBW));
getResources().getString(R.string.notification_status_text,
Util.formatSpeed(inBW), Util.formatSpeed(outBW));
String bigText =
getResources().getString(R.string.notification_status_bw,
fmt.format(inBW), fmt.format(outBW)) + '\n'
Util.formatSpeed(inBW), Util.formatSpeed(outBW)) + '\n'
+ getResources().getString(R.string.notification_status_peers,
active, known) + '\n'
+ getResources().getString(R.string.notification_status_expl,
@ -390,19 +277,26 @@ public class RouterService extends Service {
inCl, outCl);
boolean haveTunnels = inCl > 0 && outCl > 0;
if(haveTunnels != _hadTunnels) {
if (isGracefulShutdownInProgress()) {
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
_currTitle = getString(R.string.notification_status_graceful, DataHelper.formatDuration(ms));
} else {
_currTitle = getString(R.string.notification_status_stopping);
}
} else if (haveTunnels != _hadTunnels) {
if(haveTunnels) {
_currTitle = "Client tunnels are ready";
_currTitle = getString(R.string.notification_status_client_ready);
setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle);
} else {
_currTitle = "Client tunnels are down";
_currTitle = getString(R.string.notification_status_client_down);
setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, _currTitle);
}
_hadTunnels = haveTunnels;
} else if (_currTitle == null || _currTitle.equals(""))
_currTitle = "I2P is running";
_currTitle = getString(R.string.notification_status_running);
_statusBar.update(_currTitle, text, bigText);
}
@ -442,7 +336,10 @@ public class RouterService extends Service {
}
public boolean isStarted() throws RemoteException {
return mStartCalled;
return _state != State.INIT &&
_state != State.STOPPED &&
_state != State.MANUAL_STOPPED &&
_state != State.MANUAL_QUITTED;
}
public State getState() throws RemoteException {
@ -457,7 +354,7 @@ public class RouterService extends Service {
// ******** following methods may be accessed from Activities and Receivers ************
/**
* @returns null if router is not running
* @return null if router is not running
*/
public RouterContext getRouterContext() {
RouterContext rv = _context;
@ -472,7 +369,8 @@ public class RouterService extends Service {
&& _state != State.STOPPING
&& _state != State.MANUAL_STOPPING
&& _state != State.MANUAL_QUITTING
&& _state != State.NETWORK_STOPPING) {
&& _state != State.NETWORK_STOPPING
&& _state != State.GRACEFUL_SHUTDOWN) {
return null;
}
return rv;
@ -486,11 +384,15 @@ public class RouterService extends Service {
}
public boolean canManualStop() {
return _state == State.WAITING || _state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE;
return _state == State.WAITING ||
_state == State.RUNNING || _state == State.ACTIVE ||
_state == State.GRACEFUL_SHUTDOWN;
}
/**
* Stop and don't restart the router, but keep the service
*
* Apparently unused - see manualQuit()
*/
public void manualStop() {
Util.d("manualStop called"
@ -502,8 +404,9 @@ public class RouterService extends Service {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping);
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
stopperThread.start();
}
@ -523,8 +426,9 @@ public class RouterService extends Service {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping);
Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED));
stopperThread.start();
} else if(_state == State.WAITING) {
@ -544,8 +448,9 @@ public class RouterService extends Service {
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");
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping_after_net);
// don't change state, let the shutdown hook do it
Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING));
stopperThread.start();
@ -565,13 +470,97 @@ public class RouterService extends Service {
if(!canManualStart()) {
return;
}
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
_statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_starting);
setState(State.STARTING);
_starterThread = new Thread(new Starter());
_starterThread.start();
}
}
/**
* Graceful Shutdown
*
* @since 0.9.19
*/
public boolean isGracefulShutdownInProgress() {
if (_state == State.GRACEFUL_SHUTDOWN) {
RouterContext ctx = _context;
return ctx != null && ctx.router().gracefulShutdownInProgress();
}
return false;
}
private String _oldTitle;
/**
* Graceful Shutdown
*
* @since 0.9.19
*/
public void gracefulShutdown() {
Util.d("gracefulShutdown called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(!canManualStop()) {
return;
}
if(_state == State.STARTING || _state == State.WAITING) {
manualQuit();
return;
}
if(_state == State.RUNNING || _state == State.ACTIVE) {
RouterContext ctx = _context;
if(ctx != null && ctx.router().isAlive()) {
int part = ctx.tunnelManager().getParticipatingCount();
if(part <= 0) {
manualQuit();
} else {
ctx.router().shutdownGracefully();
_oldTitle = _currTitle;
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
_statusBar.replace(
StatusBar.ICON_STOPPING,
getString(R.string.notification_status_graceful, DataHelper.formatDuration(ms))
);
} else {
_statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping);
}
setState(State.GRACEFUL_SHUTDOWN);
}
}
}
}
}
/**
* Cancel Graceful Shutdown
*
* @since 0.9.19
*/
public void cancelGracefulShutdown() {
Util.d("cancelGracefulShutdown called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(_state != State.GRACEFUL_SHUTDOWN) {
return;
}
RouterContext ctx = _context;
if(ctx != null && ctx.router().isAlive()) {
ctx.router().cancelGracefulShutdown();
_currTitle = _oldTitle;
if (_hadTunnels) {
setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, R.string.notification_status_shutdown_cancelled);
} else {
setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_shutdown_cancelled);
}
}
}
}
// ******** end methods accessed from Activities and Receivers ************
private static final int STATE_MSG = 1;
@ -579,29 +568,40 @@ public class RouterService extends Service {
/**
* Our Handler used to execute operations on the main thread.
*/
private final Handler mHandler = new Handler() {
private final Handler mHandler = new StateHandler(new WeakReference<>(this));
private static class StateHandler extends Handler {
WeakReference<RouterService> mReference;
public StateHandler(WeakReference<RouterService> reference) {
mReference = reference;
}
@Override
public void handleMessage(Message msg) {
RouterService parent = mReference.get();
if (parent == null)
return;
switch (msg.what) {
case STATE_MSG:
final State state = _state;
final State state = parent._state;
// Broadcast to all clients the new state.
final int N = mStateCallbacks.beginBroadcast();
final int N = parent.mStateCallbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
mStateCallbacks.getBroadcastItem(i).stateChanged(state);
parent.mStateCallbacks.getBroadcastItem(i).stateChanged(state);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mStateCallbacks.finishBroadcast();
parent.mStateCallbacks.finishBroadcast();
break;
default:
super.handleMessage(msg);
}
}
};
}
/**
* Turn off the status bar. Unregister the receiver. If we were running,
@ -615,6 +615,9 @@ public class RouterService extends Service {
_handler.removeCallbacks(_updater);
_statusBar.remove();
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateRequested);
LocalBroadcastManager.getInstance(this).unregisterReceiver(onLocaleChanged);
I2PReceiver rcvr = _receiver;
if(rcvr != null) {
synchronized(rcvr) {
@ -631,9 +634,10 @@ public class RouterService extends Service {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING || _state == State.ACTIVE) {
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
// should this be in a thread?
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, R.string.notification_status_shutting_down);
Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
stopperThread.start();
}
@ -688,7 +692,7 @@ public class RouterService extends Service {
public void run() {
Util.d(this + " shutdown hook"
+ " Current state is: " + _state);
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, R.string.notification_status_shutting_down);
I2PReceiver rcvr = _receiver;
if(rcvr != null) {
synchronized(rcvr) {
@ -708,7 +712,8 @@ public class RouterService extends Service {
_starterThread.interrupt();
}
if(_state == State.WAITING || _state == State.STARTING
|| _state == State.RUNNING || _state == State.ACTIVE) {
|| _state == State.RUNNING || _state == State.ACTIVE
|| _state == State.GRACEFUL_SHUTDOWN) {
setState(State.STOPPING);
}
}
@ -750,7 +755,7 @@ public class RouterService extends Service {
mStateCallbacks.kill();
stopForeground(true);
stopSelf();
} else if(_state == State.MANUAL_QUITTING) {
} else if(_state == State.MANUAL_QUITTING || _state == State.GRACEFUL_SHUTDOWN) {
setState(State.MANUAL_QUITTED);
// Unregister all callbacks.
mStateCallbacks.kill();
@ -777,6 +782,13 @@ public class RouterService extends Service {
private void setState(State s) {
_state = s;
saveState();
// Broadcast the new state within this app.
Intent intent = new Intent(LOCAL_BROADCAST_STATE_CHANGED);
intent.putExtra(LOCAL_BROADCAST_EXTRA_STATE, (android.os.Parcelable) _state);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
// Notify other apps that the state has changed
mHandler.sendEmptyMessage(STATE_MSG);
}

Some files were not shown because too many files have changed in this diff Show More