Compare commits

...

242 Commits

Author SHA1 Message Date
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
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
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
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
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
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
301 changed files with 10196 additions and 4534 deletions

View File

@ -42,6 +42,7 @@ build
^app/src/main/res/drawable/i2plogo.png ^app/src/main/res/drawable/i2plogo.png
^app/src/main/res/raw/blocklist_txt ^app/src/main/res/raw/blocklist_txt
^app/src/main/res/raw/hosts_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/license_
^app/src/main/res/raw/certificates_zip ^app/src/main/res/raw/certificates_zip
^app/src/main/assets/themes/console/images ^app/src/main/assets/themes/console/images

View File

@ -1,6 +1,6 @@
[main] [main]
host = https://www.transifex.com host = https://www.transifex.com
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, 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
[I2P.android] [I2P.android]
file_filter = app/src/main/res/values-<lang>/strings.xml file_filter = app/src/main/res/values-<lang>/strings.xml

View File

@ -1,6 +1,20 @@
0.9.19 0.9.20
* 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 * Made internal state handling more stable
* Added graceful shutdown support * 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 * Improved logging
* Bug fixes and translation updates * Bug fixes and translation updates

21
TODO
View File

@ -1,23 +1,33 @@
# Fixes # 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 - Create tunnel wizard
<zzz> hmm would be nice if they could be shared-client or have an option <zzz> hmm would be nice if they could be shared-client or have an option
<zzz> was setting up email tunnels <zzz> was setting up email tunnels
- Browser - Browser
<zzzccc> Bug report: i2p browser treats 302 as an error <zzzccc> Bug report: i2p browser treats 302 as an error
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page <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 # Short-term
- Disable uPnP when on cell networks - Disable uPnP when on cell networks
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi <zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
- I2PTunnel - I2PTunnel
- Improve tunnel list status indicators
- Show all messages somewhere - Show all messages somewhere
- Improve detail page, expose advanced settings - Icons/header images for tunnel types on details page
- Add edit page
- Progress feedback for addressbook subscriptions reload - Progress feedback for addressbook subscriptions reload
- Display release notes directly on new router version - Display release notes directly on new router version
- Fill out help pages - Fill out help pages
@ -38,7 +48,6 @@
- Also look at connection type: Connectivity.isConnectionFast() - Also look at connection type: Connectivity.isConnectionFast()
- Expose log level overrides - Expose log level overrides
- Improve graphs - Improve graphs
- Show time on bottom axis
- Show fixed x range, not only available data - Show fixed x range, not only available data
- Think about pan/zoom - Think about pan/zoom
- How to persist data across restarts? - How to persist data across restarts?

View File

@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
apply plugin: 'witness' apply plugin: 'witness'
android { android {
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION as String
defaultConfig { defaultConfig {
versionCode 4745226 versionCode 4745230
versionName '0.9.18' versionName '0.9.20'
minSdkVersion 9 minSdkVersion 9
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
// For Espresso // For Espresso
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@ -56,17 +56,21 @@ dependencies {
compile project(':client') compile project(':client')
// Android Support Repository dependencies // Android Support Repository dependencies
compile 'com.android.support:support-v4:21.0.3' compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:21.0.3' compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:recyclerview-v7:21.0.3' compile 'com.android.support:recyclerview-v7:22.2.0'
// Remote dependencies // Remote dependencies
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0' compile 'net.i2p.android.ext:floatingactionbutton:1.9.0'
compile files('libs/androidplot-core-0.6.1.jar') compile files('libs/androidplot-core-0.6.1.jar')
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){ compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4' exclude module: 'support-v4'
} }
compile 'com.pnikosis:materialish-progress:1.5'
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'
}
// Testing-only dependencies // Testing-only dependencies
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
@ -75,11 +79,14 @@ dependencies {
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160', 'com.android.support:support-v4:7bb6e40a18774aa2595e4d8f9fe0ae14e61670f71a1279272fb0b79b8be71180',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5', 'com.android.support:appcompat-v7:2d5867698410b41f75140c91d6c1e58da74ae0f97baf6e0bdd1f7cc1017ceb2c',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2', 'com.android.support:recyclerview-v7:3a8da14585fa1c81f06e7cef4d93a7641f0323d8f984ff9a7bd7a6e416b46888',
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab', 'net.i2p.android.ext:floatingactionbutton:b41eae5fe6be599e3fade00273521b0914f2e199d5f04c50fa34cfe935347f76',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad', 'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
'com.eowise:recyclerview-stickyheaders:7b236da49b33b840e9ba6e7e4182218d1a2d9047236fdbc3ca947352f9b0883b',
'com.mcxiaoke.viewpagerindicator:library:1e8aad664137f68abdfee94889f6da3dc98be652a235176a403965a07a25de62',
] ]
} }
@ -114,6 +121,32 @@ task copyI2PResources(type: Copy) {
into('raw') { into('raw') {
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } } from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_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('../LICENSE.txt') { rename { 'license_app_txt' } }
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } } from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
from(i2pbase + '/licenses') { from(i2pbase + '/licenses') {
@ -134,9 +167,7 @@ task copyI2PResources(type: Copy) {
] ]
} }
rename { String name -> rename { String name ->
String part = name.substring(8, name.lastIndexOf('.txt')) name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
String.format('license_%s_txt',
part.toLowerCase(Locale.US).replace('.', '_'))
} }
} }
from certificatesZip from certificatesZip
@ -168,6 +199,7 @@ task cleanI2PResources(type: Delete) {
delete fileTree('src/main/res/raw') { delete fileTree('src/main/res/raw') {
include 'blocklist_txt' include 'blocklist_txt'
include 'hosts_txt' include 'hosts_txt'
include '*_ht'
include 'license_*' include 'license_*'
include 'certificates_zip' include 'certificates_zip'
} }

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

@ -29,10 +29,11 @@
</receiver> </receiver>
<activity <activity
android:name=".MainActivity" android:name="net.i2p.android.I2PActivity"
android:icon="@drawable/ic_launcher_itoopie" android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop"> android:launchMode="singleTop">
<!-- Console filters -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -41,31 +42,43 @@
<action android:name="net.i2p.android.router.START_I2P" /> <action android:name="net.i2p.android.router.START_I2P" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
<!-- Addressbook filters -->
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
</activity> </activity>
<activity <activity
android:name=".NewsActivity" android:name=".NewsActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/label_news" android:label="@string/label_news"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name="net.i2p.android.help.HelpActivity" android:name="net.i2p.android.help.HelpActivity"
android:label="@string/menu_help" android:label="@string/menu_help"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name="net.i2p.android.help.BrowserConfigActivity" android:name="net.i2p.android.help.BrowserConfigActivity"
android:label="@string/label_browser_configuration" android:label="@string/label_browser_configuration"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".LicenseActivity" android:name=".LicenseActivity"
@ -95,63 +108,36 @@
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"
android:label="@string/menu_settings" android:label="@string/menu_settings"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".addressbook.AddressbookSettingsActivity" android:name=".addressbook.AddressbookSettingsActivity"
android:label="Addressbook Settings" android:label="@string/label_addressbook"
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".addressbook.AddressbookActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookActivity"
android:label="@string/label_addressbook"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
</activity> </activity>
<activity <activity
android:name=".addressbook.AddressbookAddWizardActivity" android:name=".addressbook.AddressbookAddWizardActivity"
android:label="Add new Destination" android:parentActivityName="net.i2p.android.I2PActivity">
android:parentActivityName=".addressbook.AddressbookActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
android:label="@string/label_i2ptunnel"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity> </activity>
<activity <activity
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity" android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
android:label="I2PTunnel" android:parentActivityName="net.i2p.android.I2PActivity">
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.i2ptunnel.TunnelListActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name="net.i2p.android.i2ptunnel.EditTunnelActivity" android:name="net.i2p.android.i2ptunnel.preferences.EditTunnelActivity"
android:label="@string/edit_tunnel" android:label="@string/edit_tunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelDetailActivity"> android:parentActivityName="net.i2p.android.i2ptunnel.TunnelDetailActivity">
<meta-data <meta-data
@ -160,23 +146,22 @@
</activity> </activity>
<activity <activity
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity" android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
android:label="Tunnel Creation Wizard" android:parentActivityName="net.i2p.android.I2PActivity">
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".log.LogActivity" android:name=".log.LogActivity"
android:label="I2P Logs" android:label="@string/label_logs"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".log.LogDetailActivity" android:name=".log.LogDetailActivity"
android:label="Log Entry" android:label="@string/log_entry"
android:parentActivityName=".log.LogActivity"> android:parentActivityName=".log.LogActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@ -184,29 +169,29 @@
</activity> </activity>
<activity <activity
android:name=".stats.RateGraphActivity" android:name=".stats.RateGraphActivity"
android:label="Rate Graph" android:label="@string/label_graphs"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".stats.PeersActivity" android:name=".stats.PeersActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="I2P Peers and Transport Status" android:label="@string/label_peers_status"
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".netdb.NetDbActivity" android:name=".netdb.NetDbActivity"
android:label="NetDB" android:label="NetDB"
android:parentActivityName=".MainActivity"> android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" /> android:value="net.i2p.android.I2PActivity" />
</activity> </activity>
<activity <activity
android:name=".netdb.NetDbDetailActivity" android:name=".netdb.NetDbDetailActivity"

View File

@ -132,7 +132,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
if (shouldPersist()) { if (shouldPersist()) {
persistString(t); persistString(t);
} }
callChangeListener(Integer.valueOf(value)); callChangeListener(value);
} }
public void onStartTrackingTouch(SeekBar seek) { public void onStartTrackingTouch(SeekBar seek) {

View File

@ -0,0 +1,358 @@
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 net.i2p.android.widget.CustomViewPager;
import net.i2p.android.widget.SlidingTabLayout;
import net.i2p.router.RouterContext;
import java.io.File;
/**
* The main activity of the app. Contains a ViewPager that holds the three main
* views:
* <ul>
* <li>The console</li>
* <li>The addressbook</li>
* <li>The tunnel manager</li>
* </ul>
*/
public class I2PActivity extends I2PActivityBase implements
MainFragment.RouterControlListener {
CustomViewPager mViewPager;
ViewPagerAdapter mViewPagerAdapter;
SlidingTabLayout mSlidingTabLayout;
private boolean mAutoStartFromIntent = false;
private boolean _keep = true;
private boolean _startPressed = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewpager);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
mViewPager = (CustomViewPager) findViewById(R.id.pager);
mViewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
mViewPager.setAdapter(mViewPagerAdapter);
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
// Center the tabs in the layout
mSlidingTabLayout.setDistributeEvenly(true);
// Customize tab color
mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
@Override
public int getIndicatorColor(int position) {
return getResources().getColor(R.color.accent);
}
});
// Give the SlidingTabLayout the ViewPager
mSlidingTabLayout.setViewPager(mViewPager);
_keep = true;
}
public static class ViewPagerAdapter extends MemoryFragmentPagerAdapter {
private static final int NUM_ITEMS = 3;
private Context mContext;
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new ConsoleContainer();
case 1:
return new TunnelsContainer();
case 2:
return new AddressbookContainer();
default:
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return mContext.getString(R.string.label_console);
case 1:
return mContext.getString(R.string.label_tunnels);
case 2:
return mContext.getString(R.string.label_addresses);
default:
return null;
}
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
Util.d("Initializing...");
InitActivities init = new InitActivities(this);
init.debugStuff();
init.initialize();
super.onPostCreate(savedInstanceState);
handleIntents();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
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")) {
if (mViewPager.getCurrentItem() != 0)
mViewPager.setCurrentItem(0, false);
autoStart();
}
}
private void autoStart() {
if (canStart()) {
if (Connectivity.isConnected(this)) {
mAutoStartFromIntent = true;
onStartRouterClicked();
} else {
// Not connected to a network
// TODO: Notify user
}
} else {
// TODO: Notify user
}
}
@Override
public void onStart() {
super.onStart();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
}
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.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Build; import android.os.Build;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util; import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil; import net.i2p.util.FileUtil;
@ -93,15 +94,23 @@ class InitActivities {
docsDir.mkdir(); docsDir.mkdir();
copyResourceToFile(R.raw.ahelper_conflict_header_ht, "docs/ahelper-conflict-header.ht"); 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_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.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.denied_header_ht, "docs/denied-header.ht");
copyResourceToFile(R.raw.dnf_header_ht, "docs/dnf-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.dnfb_header_ht, "docs/dnfb-header.ht");
copyResourceToFile(R.raw.dnfh_header_ht, "docs/dnfh-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.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.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.noproxy_header_ht, "docs/noproxy-header.ht");
copyResourceToFile(R.raw.protocol_header_ht, "docs/protocol-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"); File cssDir = new File(docsDir, "themes/console/light");
cssDir.mkdirs(); cssDir.mkdirs();

View File

@ -1,22 +1,40 @@
package net.i2p.android.apps; package net.i2p.android.apps;
import java.io.File; import android.content.Context;
import net.i2p.android.router.NewsActivity; import net.i2p.android.router.NewsActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Notifications; import net.i2p.android.router.util.Notifications;
import net.i2p.crypto.SU3File;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext; 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.router.util.RFC822Date;
import net.i2p.util.EepGet; import net.i2p.util.EepGet;
import net.i2p.util.FileUtil; import net.i2p.util.FileUtil;
import net.i2p.util.Log; 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 * From router console, simplified since we don't deal with router versions
* or updates. * or updates.
*/ */
public class NewsFetcher implements Runnable, EepGet.StatusListener { public class NewsFetcher implements Runnable, EepGet.StatusListener {
private final Context mCtx;
private final RouterContext _context; private final RouterContext _context;
private final Notifications _notif; private final Notifications _notif;
private final Log _log; private final Log _log;
@ -35,10 +53,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} }
public static /* final */ synchronized NewsFetcher getInstance( public static /* final */ synchronized NewsFetcher getInstance(
RouterContext ctx, Notifications notif) { Context context, RouterContext ctx, Notifications notif) {
if (_instance != null) if (_instance != null)
return _instance; return _instance;
_instance = new NewsFetcher(ctx, notif); _instance = new NewsFetcher(context, ctx, notif);
return _instance; return _instance;
} }
@ -47,20 +65,21 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private static final String TEMP_NEWS_FILE = "news.xml.temp"; private static final String TEMP_NEWS_FILE = "news.xml.temp";
/** /**
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi. * Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
* We may be able to change it to psi.i2p in a future release after * We may be able to change it to psi.i2p in a future release after
* the hostname propagates. * the hostname propagates.
* *
* @since 0.7.14 not configurable * @since 0.7.14 not configurable
*/ */
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml"; private static final String BACKUP_NEWS_URL_SU3 = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.su3";
private static final String PROP_LAST_CHECKED = "router.newsLastChecked"; private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency"; private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + ""; private static final String DEFAULT_REFRESH_FREQUENCY = 24 * 60 * 60 * 1000 + "";
private static final String PROP_NEWS_URL = "router.newsURL"; private static final String PROP_NEWS_URL = "router.newsURL";
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml"; public static final String DEFAULT_NEWS_URL_SU3 = "http://echelon.i2p/news/news.su3";
private NewsFetcher(RouterContext ctx, Notifications notif) { private NewsFetcher(Context context, RouterContext ctx, Notifications notif) {
mCtx = context;
_context = ctx; _context = ctx;
_notif = notif; _notif = notif;
_context.addShutdownTask(new Shutdown()); _context.addShutdownTask(new Shutdown());
@ -69,7 +88,8 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
String last = ctx.getProperty(PROP_LAST_CHECKED); String last = ctx.getProperty(PROP_LAST_CHECKED);
if (last != null) if (last != null)
_lastFetch = Long.parseLong(last); _lastFetch = Long.parseLong(last);
} catch (NumberFormatException nfe) {} } catch (NumberFormatException nfe) {
}
File newsDir = new File(_context.getRouterDir(), NEWS_DIR); File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
// isn't already there on android // isn't already there on android
newsDir.mkdir(); newsDir.mkdir();
@ -94,24 +114,24 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} }
public String status() { public String status() {
StringBuilder buf = new StringBuilder(128); StringBuilder buf = new StringBuilder(128);
long now = _context.clock().now(); long now = _context.clock().now();
if (_lastUpdated > 0) { if (_lastUpdated > 0) {
buf.append(Translate.getString("News last updated {0} ago.", buf.append(mCtx.getString(R.string.news_last_updated,
DataHelper.formatDuration2(now - _lastUpdated), DataHelper.formatDuration2(now - _lastUpdated)))
_context, "foo")) .append('\n');
.append('\n'); }
} if (_lastFetch > _lastUpdated) {
if (_lastFetch > _lastUpdated) { buf.append(mCtx.getString(R.string.news_last_checked,
buf.append(Translate.getString("News last checked {0} ago.", DataHelper.formatDuration2(now - _lastFetch)));
DataHelper.formatDuration2(now - _lastFetch), }
_context, "foo")); return buf.toString();
}
return buf.toString();
} }
private static final long INITIAL_DELAY = 5*60*1000; // Runnable
private static final long RUN_DELAY = 30*60*1000;
private static final long INITIAL_DELAY = 5 * 60 * 1000;
private static final long RUN_DELAY = 30 * 60 * 1000;
public void run() { public void run() {
_thread = Thread.currentThread(); _thread = Thread.currentThread();
@ -137,7 +157,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
return true; return true;
updateLastFetched(); updateLastFetched();
String freq = _context.getProperty(PROP_REFRESH_FREQUENCY, String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
DEFAULT_REFRESH_FREQUENCY); DEFAULT_REFRESH_FREQUENCY);
try { try {
long ms = Long.parseLong(freq); long ms = Long.parseLong(freq);
if (ms <= 0) if (ms <= 0)
@ -158,8 +178,9 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} }
/** /**
* Call this when changing news URLs to force an update next time the timer fires. * Call this when changing news URLs to force an update next time the timer fires.
* @since 0.8.7 *
* @since 0.8.7
*/ */
void invalidateNews() { void invalidateNews() {
_lastModified = null; _lastModified = null;
@ -167,7 +188,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} }
public void fetchNews() { public void fetchNews() {
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL); String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL_SU3);
String proxyHost = "127.0.0.1"; String proxyHost = "127.0.0.1";
int proxyPort = 4444; int proxyPort = 4444;
if (_tempFile.exists()) if (_tempFile.exists())
@ -183,7 +204,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} else { } else {
// backup news location - always proxied // backup news location - always proxied
_tempFile.delete(); _tempFile.delete();
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified); get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL_SU3, true, null, _lastModified);
get.addStatusListener(this); get.addStatusListener(this);
if (get.fetch()) if (get.fetch())
_lastModified = get.getLastModified(); _lastModified = get.getLastModified();
@ -193,25 +214,38 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} }
} }
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { // EepGet.StatusListener
// ignore
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) { public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
// ignore // ignore
} }
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) { public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred)); _log.info("News fetched from " + url + " with " + (alreadyTransferred + bytesTransferred));
long now = _context.clock().now(); long now = _context.clock().now();
if (_tempFile.exists()) { if (_tempFile.exists() && _tempFile.length() > 0) {
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true); File from;
if (url.endsWith(".su3")) {
try {
from = processSU3();
} catch (IOException ioe) {
_log.error("Failed to extract the news file", ioe);
_tempFile.delete();
return;
}
} else {
from = _tempFile;
}
boolean copied = FileUtil.rename(from, _newsFile);
_tempFile.delete();
if (copied) { if (copied) {
_lastUpdated = now; _lastUpdated = now;
_tempFile.delete();
// Notify user // Notify user
_notif.notify("News Updated", "Touch to view latest I2P news", _notif.notify(mCtx.getString(R.string.news_updated),
mCtx.getString(R.string.view_news),
NewsActivity.class); NewsActivity.class);
} else { } else {
if (_log.shouldLog(Log.ERROR)) if (_log.shouldLog(Log.ERROR))
@ -226,13 +260,21 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_context.router().saveConfig(); _context.router().saveConfig();
} }
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (_log.shouldLog(Log.WARN)) if (_log.shouldLog(Log.WARN))
_log.warn("Failed to fetch the news from " + url); _log.warn("Failed to fetch the news from " + url);
_tempFile.delete(); _tempFile.delete();
} }
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {} public void headerReceived(String url, int attemptNum, String key, String val) {
}
public void attempting(String url) {
}
private class Shutdown implements Runnable { private class Shutdown implements Runnable {
public void run() { public void run() {
@ -241,4 +283,138 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_thread.interrupt(); _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

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

View File

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

View File

@ -19,6 +19,7 @@ import android.view.ViewGroup;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.android.router.util.BetterAsyncTaskLoader; import net.i2p.android.router.util.BetterAsyncTaskLoader;
import net.i2p.android.widget.DividerItemDecoration;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@ -55,6 +56,9 @@ public class BrowserListFragment extends Fragment implements
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false); View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list); RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
// use a linear layout manager // use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity()); RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setLayoutManager(mLayoutManager);

View File

@ -4,7 +4,7 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder; import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -12,8 +12,9 @@ import android.view.MenuItem;
import net.i2p.android.router.LicenseActivity; import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog; import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.util.LocaleManager;
public class HelpActivity extends ActionBarActivity implements public class HelpActivity extends AppCompatActivity implements
HelpListFragment.OnEntrySelectedListener { HelpListFragment.OnEntrySelectedListener {
public static final String CATEGORY = "help_category"; public static final String CATEGORY = "help_category";
public static final int CAT_MAIN = 0; public static final int CAT_MAIN = 0;
@ -28,8 +29,11 @@ public class HelpActivity extends ActionBarActivity implements
private boolean mTwoPane; private boolean mTwoPane;
private int mCategory; private int mCategory;
private final LocaleManager localeManager = new LocaleManager();
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help); setContentView(R.layout.activity_help);
@ -58,6 +62,12 @@ public class HelpActivity extends ActionBarActivity implements
} }
} }
@Override
public void onResume() {
super.onResume();
localeManager.onResume(this);
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_help_actions, menu); getMenuInflater().inflate(R.menu.activity_help_actions, menu);

View File

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

View File

@ -1,27 +0,0 @@
package net.i2p.android.i2ptunnel;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import net.i2p.android.router.R;
public class EditTunnelActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help_onepane);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
EditTunnelFragment editFrag = EditTunnelFragment.newInstance(tunnelId);
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, editFrag).commit();
}
}
}

View File

@ -2,24 +2,51 @@ package net.i2p.android.i2ptunnel;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.Toolbar;
import android.view.View;
import net.i2p.android.router.I2PActivityBase; import net.i2p.android.I2PActivityBase;
import net.i2p.android.i2ptunnel.preferences.EditTunnelActivity;
import net.i2p.android.router.R; import net.i2p.android.router.R;
public class TunnelDetailActivity extends I2PActivityBase implements public class TunnelDetailActivity extends I2PActivityBase implements
TunnelDetailFragment.TunnelDetailListener { TunnelDetailFragment.TunnelDetailListener {
private boolean transitionReversed;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false); transitionReversed = false;
getSupportActionBar().setDisplayShowTitleEnabled(false);
if (savedInstanceState == null) { if (savedInstanceState == null) {
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0); int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId); TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, detailFrag).commit(); .add(android.R.id.content, detailFrag).commit();
}
}
@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);
} }
} }

View File

@ -1,16 +1,19 @@
package net.i2p.android.i2ptunnel; package net.i2p.android.i2ptunnel;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -21,6 +24,8 @@ import android.widget.Toast;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil; import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.android.util.FragmentUtils;
import net.i2p.app.ClientAppState;
import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.List; import java.util.List;
@ -31,6 +36,7 @@ public class TunnelDetailFragment extends Fragment {
TunnelDetailListener mCallback; TunnelDetailListener mCallback;
private TunnelControllerGroup mGroup; private TunnelControllerGroup mGroup;
private TunnelEntry mTunnel; private TunnelEntry mTunnel;
private Toolbar mToolbar;
public static TunnelDetailFragment newInstance(int tunnelId) { public static TunnelDetailFragment newInstance(int tunnelId) {
TunnelDetailFragment f = new TunnelDetailFragment(); TunnelDetailFragment f = new TunnelDetailFragment();
@ -42,8 +48,8 @@ public class TunnelDetailFragment extends Fragment {
// Container Activity must implement this interface // Container Activity must implement this interface
public interface TunnelDetailListener { public interface TunnelDetailListener {
public void onEditTunnel(int tunnelId); void onEditTunnel(int tunnelId);
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft); void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
} }
@Override @Override
@ -52,19 +58,15 @@ public class TunnelDetailFragment extends Fragment {
// This makes sure that the container activity has implemented // This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception // the callback interface. If not, it throws an exception
try { mCallback = FragmentUtils.getParent(this, TunnelDetailListener.class);
mCallback = (TunnelDetailListener) activity; if (mCallback == null)
} catch (ClassCastException e) { throw new ClassCastException("Parent must implement TunnelDetailListener");
throw new ClassCastException(activity.toString()
+ " must implement OnTunnelDeletedListener");
}
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
String error; String error;
try { try {
@ -90,6 +92,16 @@ public class TunnelDetailFragment extends Fragment {
Bundle savedInstanceState) { Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false); View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
mToolbar = (Toolbar) v.findViewById(R.id.detail_toolbar);
mToolbar.inflateMenu(R.menu.fragment_i2ptunnel_detail_actions);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return onToolbarItemSelected(menuItem);
}
});
updateToolbar();
if (mTunnel != null) { if (mTunnel != null) {
TextView name = (TextView) v.findViewById(R.id.tunnel_name); TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(mTunnel.getName()); name.setText(mTunnel.getName());
@ -103,34 +115,39 @@ public class TunnelDetailFragment extends Fragment {
TextView details = (TextView) v.findViewById(R.id.tunnel_details); TextView details = (TextView) v.findViewById(R.id.tunnel_details);
details.setText(mTunnel.getDetails()); details.setText(mTunnel.getDetails());
View accessIfacePortLabel = v.findViewById(R.id.tunnel_access_interface_port_label); View accessIfacePortItem = v.findViewById(R.id.tunnel_access_interface_port_item);
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port); TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
View targetIfacePortLabel = v.findViewById(R.id.tunnel_target_interface_port_label); 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); TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
View targetIfaceOpen = v.findViewById(R.id.tunnel_target_open);
switch (mTunnel.getInternalType()) { switch (mTunnel.getInternalType()) {
case "httpbidirserver": case "httpbidirserver":
accessIfacePort.setText(mTunnel.getClientLink(false)); accessIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(accessIfaceOpen, true);
v.findViewById(R.id.icon_link_access).setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false)); targetIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(targetIfaceOpen, false);
break; break;
case "streamrserver": case "streamrserver":
accessIfacePort.setText(mTunnel.getServerLink(false)); accessIfacePort.setText(mTunnel.getServerLink(false));
targetIfacePortLabel.setVisibility(View.GONE); setupOpen(accessIfaceOpen, true);
targetIfacePort.setVisibility(View.GONE); targetIfacePortItem.setVisibility(View.GONE);
break; break;
case "streamrclient": case "streamrclient":
accessIfacePortLabel.setVisibility(View.GONE); accessIfacePortItem.setVisibility(View.GONE);
accessIfacePort.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getClientLink(false)); targetIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(targetIfaceOpen, false);
break; break;
default: default:
if (mTunnel.isClient()) { if (mTunnel.isClient()) {
accessIfacePort.setText(mTunnel.getClientLink(false)); accessIfacePort.setText(mTunnel.getClientLink(false));
targetIfacePortLabel.setVisibility(View.GONE); setupOpen(accessIfaceOpen, true);
targetIfacePort.setVisibility(View.GONE); targetIfacePortItem.setVisibility(View.GONE);
} else { } else {
accessIfacePortLabel.setVisibility(View.GONE); accessIfacePortItem.setVisibility(View.GONE);
accessIfacePort.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false)); targetIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(targetIfaceOpen, false);
} }
} }
@ -141,19 +158,50 @@ public class TunnelDetailFragment extends Fragment {
return v; return v;
} }
@Override private void setupOpen(View open, final boolean client) {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (mTunnel.isRunning() &&
inflater.inflate(R.menu.fragment_i2ptunnel_detail_actions, menu); (client ? mTunnel.isClientLinkValid() : mTunnel.isServerLinkValid())) {
// Disable until ticket #815 is closed open.setVisibility(View.VISIBLE);
menu.findItem(R.id.action_edit_tunnel).setVisible(false); open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(client ? mTunnel.getClientLink(true) : mTunnel.getServerLink(true)));
try {
startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = mTunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
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 private void updateToolbar() {
public void onPrepareOptionsMenu(Menu menu) { Menu menu = mToolbar.getMenu();
MenuItem start = menu.findItem(R.id.action_start_tunnel); MenuItem start = menu.findItem(R.id.action_start_tunnel);
MenuItem stop = menu.findItem(R.id.action_stop_tunnel); MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
if (mTunnel != null) { if (mTunnel != null && mGroup != null &&
(mGroup.getState() == ClientAppState.STARTING ||
mGroup.getState() == ClientAppState.RUNNING)) {
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING; boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
start.setVisible(isStopped); start.setVisible(isStopped);
@ -170,8 +218,7 @@ public class TunnelDetailFragment extends Fragment {
} }
} }
@Override private boolean onToolbarItemSelected(MenuItem item) {
public boolean onOptionsItemSelected(MenuItem item) {
if (mTunnel == null) if (mTunnel == null)
return false; return false;
@ -182,16 +229,16 @@ public class TunnelDetailFragment extends Fragment {
Toast.makeText(getActivity().getApplicationContext(), Toast.makeText(getActivity().getApplicationContext(),
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting) getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show(); + ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// Reload the action bar to change the start/stop action // Reload the toolbar to change the start/stop action
getActivity().supportInvalidateOptionsMenu(); updateToolbar();
return true; return true;
case R.id.action_stop_tunnel: case R.id.action_stop_tunnel:
mTunnel.getController().stopTunnel(); mTunnel.getController().stopTunnel();
Toast.makeText(getActivity().getApplicationContext(), Toast.makeText(getActivity().getApplicationContext(),
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping) getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show(); + ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// Reload the action bar to change the start/stop action // Reload the toolbar to change the start/stop action
getActivity().supportInvalidateOptionsMenu(); updateToolbar();
return true; return true;
case R.id.action_edit_tunnel: case R.id.action_edit_tunnel:
mCallback.onEditTunnel(mTunnel.getId()); mCallback.onEditTunnel(mTunnel.getId());

View File

@ -1,94 +1,174 @@
package net.i2p.android.i2ptunnel; package net.i2p.android.i2ptunnel;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.v4.util.Pair;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.android.util.FragmentUtils;
import java.util.List; import java.util.List;
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> { public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final LayoutInflater mInflater; 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) { public static class SimpleViewHolder extends RecyclerView.ViewHolder {
super(context, android.R.layout.simple_list_item_2); public SimpleViewHolder(View itemView) {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); super(itemView);
}
} }
public void setData(List<TunnelEntry> tunnels) { public static class TunnelViewHolder extends RecyclerView.ViewHolder {
clear(); public ImageView status;
if (tunnels != null) { public TextView name;
for (TunnelEntry tunnel : tunnels) { public TextView description;
add(tunnel); 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 @Override
public View getView(int position, View convertView, ViewGroup parent) { public int getItemViewType(int position) {
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false); if (mTunnels == null)
final TunnelEntry tunnel = getItem(position); return R.string.router_not_running;
else if (mTunnels.isEmpty())
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status); return R.layout.listitem_empty;
status.setImageDrawable(tunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
status.setBackgroundDrawable(tunnel.getStatusBackground());
else else
status.setBackground(tunnel.getStatusBackground()); return R.layout.listitem_i2ptunnel;
}
TextView name = (TextView) v.findViewById(R.id.tunnel_name); // Create new views (invoked by the layout manager)
name.setText(tunnel.getName()); @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int vt = viewType;
if (viewType == R.string.router_not_running)
vt = R.layout.listitem_empty;
TextView type = (TextView) v.findViewById(R.id.tunnel_description); View v = LayoutInflater.from(parent.getContext())
type.setText(tunnel.getDescription()); .inflate(vt, parent, false);
switch (viewType) {
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port); case R.layout.listitem_i2ptunnel:
ifacePort.setText(tunnel.getTunnelLink(false)); return new TunnelViewHolder(v);
default:
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) { return new SimpleViewHolder(v);
View open = v.findViewById(R.id.tunnel_open);
open.setVisibility(View.VISIBLE);
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
try {
getContext().startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = tunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
getContext().startActivity(intent);
}
}
})
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
}
}
});
} }
}
return v; // 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));
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);
mListener.onTunnelSelected(tunnel.getId(),
Pair.create((View)tvh.name, mCtx.getString(R.string.TUNNEL_NAME)),
Pair.create((View)tvh.description, mCtx.getString(R.string.TUNNEL_DESCRIPTION)));
}
});
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

@ -4,8 +4,10 @@ import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.util.Util;
import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.RouterContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -26,6 +28,12 @@ public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
@Override @Override
public List<TunnelEntry> loadInBackground() { public List<TunnelEntry> loadInBackground() {
// 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<TunnelEntry> ret = new ArrayList<>();
List<TunnelController> controllers = mGroup.getControllers(); List<TunnelController> controllers = mGroup.getControllers();
for (int i = 0; i < controllers.size(); i++) { for (int i = 0; i < controllers.size(); i++) {

View File

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

View File

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

View File

@ -1,13 +1,13 @@
package net.i2p.android.i2ptunnel; package net.i2p.android.i2ptunnel;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel; import net.i2p.android.wizard.model.AbstractWizardModel;
@ -32,7 +32,7 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(TunnelListFragment.TUNNEL_WIZARD_DATA, mWizardModel.save()); result.putExtra(TunnelsContainer.TUNNEL_WIZARD_DATA, mWizardModel.save());
setResult(Activity.RESULT_OK, result); setResult(Activity.RESULT_OK, result);
dialog.dismiss(); dialog.dismiss();
finish(); finish();

View File

@ -0,0 +1,289 @@
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> tunnelName,
Pair<View, String> tunnelDescription) {
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(), tunnelName, tunnelDescription);
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.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.support.v7.app.AlertDialog;
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,89 @@
package net.i2p.android.i2ptunnel.preferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.support.v4.preference.PreferenceFragment;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
protected static final String ARG_TUNNEL_ID = "tunnelId";
protected TunnelControllerGroup mGroup;
protected int mTunnelId;
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
// TODO Show error
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
loadPreferences();
}
}
@Override
public void onPause() {
super.onPause();
// Pre-Honeycomb: onPause() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
@Override
public void onStop() {
super.onStop();
// Honeycomb and above: onStop() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
private void saveTunnel() {
if (mGroup != null) {
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
}
}
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

@ -1,28 +1,29 @@
package net.i2p.android.i2ptunnel; package net.i2p.android.i2ptunnel.preferences;
import android.os.Build; import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.support.v4.preference.PreferenceFragment; import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelLogic; import net.i2p.android.i2ptunnel.util.TunnelLogic;
import net.i2p.android.i2ptunnel.util.TunnelUtil; import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.util.Addresses;
import net.i2p.i2ptunnel.ui.TunnelConfig;
public class EditTunnelFragment extends PreferenceFragment { import java.util.Set;
private static final String ARG_TUNNEL_ID = "tunnelId";
private TunnelControllerGroup mGroup; public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragment {
private int mTunnelId; private CheckBoxPreference persistentKeys;
public static EditTunnelFragment newInstance(int tunnelId) { public static GeneralTunnelPreferenceFragment newInstance(int tunnelId) {
EditTunnelFragment f = new EditTunnelFragment(); GeneralTunnelPreferenceFragment f = new GeneralTunnelPreferenceFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(ARG_TUNNEL_ID, tunnelId); args.putInt(ARG_TUNNEL_ID, tunnelId);
f.setArguments(args); f.setArguments(args);
@ -30,65 +31,27 @@ public class EditTunnelFragment extends PreferenceFragment {
} }
@Override @Override
public void onCreate(Bundle paramBundle) { protected void loadPreferences() {
super.onCreate(paramBundle);
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
// TODO Show error
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
loadPreferences();
}
}
@Override
public void onPause() {
super.onPause();
// Pre-Honeycomb: onPause() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
@Override
public void onStop() {
super.onStop();
// Honeycomb and above: onStop() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
private void saveTunnel() {
if (mGroup != null) {
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
}
}
private void loadPreferences() {
String type = TunnelUtil.getController(mGroup, mTunnelId).getType(); String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
new TunnelPreferences(type).runLogic(); 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 { class TunnelPreferences extends TunnelLogic {
PreferenceScreen ps; PreferenceScreen ps;
PreferenceCategory generalCategory; PreferenceCategory generalCategory;
PreferenceCategory portCategory; PreferenceCategory portCategory;
PreferenceScreen advanced;
PreferenceCategory tunParamCategory;
public TunnelPreferences(String type) { public TunnelPreferences(String type) {
super(type); super(type);
@ -107,6 +70,38 @@ public class EditTunnelFragment extends PreferenceFragment {
@Override @Override
protected void generalClient() { protected void generalClient() {
addPreferencesFromResource(R.xml.tunnel_gen_client, generalCategory); 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 @Override
@ -126,8 +121,26 @@ public class EditTunnelFragment extends PreferenceFragment {
@Override @Override
protected void generalClientPortStreamr(boolean isStreamr) { protected void generalClientPortStreamr(boolean isStreamr) {
ListPreference reachableBy = (ListPreference) portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE));
if (isStreamr) if (isStreamr)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE))); 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();
String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
reachableBy.setEntries(interfaces);
reachableBy.setEntryValues(interfaces);
reachableBy.setEnabled(true);
return null;
}
}.execute();
} }
@Override @Override
@ -166,6 +179,8 @@ public class EditTunnelFragment extends PreferenceFragment {
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL))); portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
if (isStreamr) if (isStreamr)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT))); portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT)));
setupReachableBy((ListPreference) portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE)));
} }
@Override @Override
@ -183,90 +198,61 @@ public class EditTunnelFragment extends PreferenceFragment {
@Override @Override
protected void advanced() { protected void advanced() {
addPreferencesFromResource(R.xml.tunnel_adv); Preference advanced = new Preference(getActivity());
advanced = (PreferenceScreen) ps.findPreference( advanced.setKey(getString(R.string.TUNNEL_CAT_ADVANCED));
getString(R.string.TUNNEL_CAT_ADVANCED)); advanced.setTitle(R.string.settings_label_advanced);
tunParamCategory = (PreferenceCategory) ps.findPreference( advanced.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
getString(R.string.TUNNEL_CAT_TUNNEL_PARAMS)); @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 @Override
protected void advancedStreamr(boolean isStreamr) { protected void advancedStreamr(boolean isStreamr) {
if (isStreamr)
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_PROFILE)));
} }
@Override @Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) { protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (isServerOrStreamrClient)
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_DELAY_CONNECT)));
} }
@Override @Override
protected void advancedServer() { protected void advancedServer() {
addPreferencesFromResource(R.xml.tunnel_adv_server, advanced);
} }
@Override @Override
protected void advancedServerHttp(boolean isHttp) { protected void advancedServerHttp(boolean isHttp) {
if (isHttp)
addPreferencesFromResource(R.xml.tunnel_adv_server_http, advanced);
else {
PreferenceCategory accessCtlCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_ACCESS_CONTROL));
accessCtlCategory.removePreference(accessCtlCategory.findPreference(getString(R.string.TUNNEL_OPT_REJECT_INPROXY)));
}
} }
@Override @Override
protected void advancedIdle() { protected void advancedIdle() {
addPreferencesFromResource(R.xml.tunnel_adv_idle, advanced);
} }
@Override @Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) { protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (isServerOrStreamrClient)
advanced.removePreference(advanced.findPreference(getString(R.string.TUNNEL_OPT_DELAY_OPEN)));
} }
@Override @Override
protected void advancedClient() { protected void advancedClient() {
PreferenceCategory idleCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_IDLE)
);
addPreferencesFromResource(R.xml.tunnel_adv_idle_client, idleCategory);
} }
@Override @Override
protected void advancedClientHttp() { protected void advancedClientHttp() {
addPreferencesFromResource(R.xml.tunnel_adv_client_http, advanced);
} }
@Override @Override
protected void advancedClientProxy() { protected void advancedClientProxy() {
addPreferencesFromResource(R.xml.tunnel_adv_client_proxy, advanced);
} }
@Override @Override
protected void advancedOther() { protected void advancedOther() {
addPreferencesFromResource(R.xml.tunnel_adv_other, advanced);
}
}
/**
* http://stackoverflow.com/a/20806812
*
* @param id the Preferences XML to load
* @param newParent the parent PreferenceGroup to add the new Preferences to.
*/
private void addPreferencesFromResource (int id, PreferenceGroup newParent) {
PreferenceScreen screen = getPreferenceScreen();
int last = screen.getPreferenceCount();
addPreferencesFromResource(id);
while (screen.getPreferenceCount () > last) {
Preference p = screen.getPreference (last);
screen.removePreference(p); // decreases the preference count
newParent.addPreference(p);
} }
} }
} }

View File

@ -225,7 +225,7 @@ public class TunnelUtil extends GeneralHelper {
ed.putInt(res.getString(R.string.TUNNEL_OPT_QUANTITY), ed.putInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY))); getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY), ed.putInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY))); getTunnelBackupQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
} }
@Override @Override

View File

@ -0,0 +1,60 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import android.preference.Preference;
import android.support.v4.app.Fragment;
import android.support.v4.preference.PreferenceFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class AdvancedPreferenceFragment extends PreferenceFragment {
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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
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,36 @@
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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
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,20 @@
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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
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,108 @@
package net.i2p.android.preferences;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
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.support.v4.preference.PreferenceFragment;
import android.widget.Toast;
import net.i2p.I2PAppContext;
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 class I2PreferenceFragment extends PreferenceFragment {
@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,54 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import android.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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
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,20 @@
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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
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,122 @@
package net.i2p.android.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.util.PortPreference;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
public class TransportsPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
// 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,128 @@
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 advanced = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(
"i2pandroid.main.showStats", false);
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);
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<>(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; package net.i2p.android.router;
public interface I2PConstants { 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; 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.CommSystemFacade;
import net.i2p.router.NetworkDatabaseFacade; import net.i2p.router.NetworkDatabaseFacade;
import net.i2p.router.Router; import net.i2p.router.Router;
@ -8,40 +12,12 @@ import net.i2p.router.TunnelManagerFacade;
import net.i2p.router.peermanager.ProfileOrganizer; import net.i2p.router.peermanager.ProfileOrganizer;
import net.i2p.router.transport.FIFOBandwidthLimiter; import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.stat.StatManager; import net.i2p.stat.StatManager;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
public class I2PFragmentBase extends Fragment { public class I2PFragmentBase extends Fragment {
private boolean mOnActivityCreated; private boolean mOnActivityCreated;
RouterContextProvider mCallback;
public static final String PREF_INSTALLED_VERSION = "app.version"; public static final String PREF_INSTALLED_VERSION = "app.version";
public interface RouterContextUser {
public void onRouterBind();
}
// Container Activity must implement this interface
public interface RouterContextProvider {
public RouterContext getRouterContext();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (RouterContextProvider) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterContextProvider");
}
}
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
@ -64,7 +40,7 @@ public class I2PFragmentBase extends Fragment {
public void onRouterConnectionNotReady() {} public void onRouterConnectionNotReady() {}
protected RouterContext getRouterContext() { protected RouterContext getRouterContext() {
return mCallback.getRouterContext(); return Util.getRouterContext();
} }
protected Router getRouter() { protected Router getRouter() {

View File

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

View File

@ -1,243 +0,0 @@
package net.i2p.android.router;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.dialog.AboutDialog;
import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import java.io.File;
public class MainActivity extends I2PActivityBase implements
MainFragment.RouterControlListener {
MainFragment mMainFragment = null;
private boolean mAutoStartFromIntent = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start with the home view
if (savedInstanceState == null) {
mMainFragment = new MainFragment();
mMainFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, mMainFragment).commit();
}
// Open nav drawer if the user has never opened it themselves
if (!getPref(PREF_NAV_DRAWER_OPENED, false))
mDrawerLayout.openDrawer(mDrawerList);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
Util.d("Initializing...");
InitActivities init = new InitActivities(this);
init.debugStuff();
init.initialize();
super.onPostCreate(savedInstanceState);
handleIntents();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntents();
}
private void handleIntents() {
if (getIntent() == null)
return;
Intent intent = getIntent();
String action = intent.getAction();
if (action == null)
return;
if (action.equals("net.i2p.android.router.START_I2P")) {
autoStart();
}
}
private void autoStart() {
if (canStart()) {
if (Connectivity.isConnected(this)) {
mAutoStartFromIntent = true;
onStartRouterClicked();
} else {
// Not connected to a network
// TODO: Notify user
}
} else {
// TODO: Notify user
}
}
@Override
public void onStart() {
super.onStart();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
lbm.sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
}
private State lastRouterState = null;
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (lastRouterState == null || lastRouterState != state) {
if (mMainFragment == null)
mMainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (mMainFragment != null) {
mMainFragment.updateState(state);
lastRouterState = state;
}
if (state == State.RUNNING && mAutoStartFromIntent) {
MainActivity.this.setResult(RESULT_OK);
finish();
}
}
}
};
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_main_actions, menu);
inflater.inflate(R.menu.activity_base_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
return true;
case R.id.menu_about:
AboutDialog dialog = new AboutDialog();
dialog.show(getSupportFragmentManager(), "about");
return true;
case R.id.menu_help:
Intent hi = new Intent(MainActivity.this, HelpActivity.class);
startActivity(hi);
return true;
case R.id.menu_help_release_notes:
TextResourceDialog rDdialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
rDdialog.setArguments(args);
rDdialog.show(getSupportFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateChange);
}
private boolean canStart() {
RouterService svc = _routerService;
return (svc == null) || (!_isBound) || svc.canManualStart();
}
private boolean canStop() {
RouterService svc = _routerService;
return svc != null && _isBound && svc.canManualStop();
}
// MainFragment.RouterControlListener
public boolean shouldShowOnOff() {
return (canStart() && Connectivity.isConnected(this)) || (canStop() && !isGracefulShutdownInProgress());
}
public boolean shouldBeOn() {
String action = getIntent().getAction();
return (canStop()) ||
(action != null && action.equals("net.i2p.android.router.START_I2P"));
}
public void onStartRouterClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, true);
svc.manualStart();
} else {
(new File(Util.getFileDir(this), "wrapper.log")).delete();
startRouter();
}
}
public boolean onStopRouterClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.manualQuit();
return true;
}
return false;
}
/** @since 0.9.19 */
public boolean isGracefulShutdownInProgress() {
RouterService svc = _routerService;
return svc != null && svc.isGracefulShutdownInProgress();
}
/** @since 0.9.19 */
public boolean onGracefulShutdownClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.gracefulShutdown();
return true;
}
return false;
}
/** @since 0.9.19 */
public boolean onCancelGracefulShutdownClicked() {
RouterService svc = _routerService;
if(svc != null && _isBound) {
setPref(PREF_AUTO_START, false);
svc.cancelGracefulShutdown();
return true;
}
return false;
}
}

View File

@ -1,10 +1,17 @@
package net.i2p.android.router; package net.i2p.android.router;
import android.app.Activity; import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -18,8 +25,10 @@ import android.widget.TableRow;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import net.i2p.android.router.dialog.ConfigureBrowserDialog; import net.i2p.android.I2PActivityBase;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.router.dialog.FirstStartDialog; import net.i2p.android.router.dialog.FirstStartDialog;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State; import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity; import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.LongToggleButton; import net.i2p.android.router.util.LongToggleButton;
@ -30,10 +39,8 @@ import net.i2p.data.Hash;
import net.i2p.data.LeaseSet; import net.i2p.data.LeaseSet;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings; import net.i2p.router.TunnelPoolSettings;
import net.i2p.util.Translate;
import java.text.Collator; import java.text.Collator;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -45,8 +52,22 @@ public class MainFragment extends I2PFragmentBase {
private Runnable _updater; private Runnable _updater;
private Runnable _oneShotUpdate; private Runnable _oneShotUpdate;
private String _savedStatus; private String _savedStatus;
private boolean _keep = true;
private boolean _startPressed = false; private ImageView mConsoleLights;
private LongToggleButton mOnOffButton;
private LinearLayout vGracefulButtons;
private ScrollView mScrollView;
private View vStatusContainer;
private ImageView vNetStatusLevel;
private TextView vNetStatusText;
private View vNonNetStatus;
private TextView vUptime;
private TextView vActive;
private TextView vKnown;
private TableLayout vTunnels;
private LinearLayout vAdvStatus;
private TextView vAdvStatusText;
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser"; private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
private static final String PREF_FIRST_START = "app.router.firstStart"; private static final String PREF_FIRST_START = "app.router.firstStart";
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats"; private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
@ -56,16 +77,28 @@ public class MainFragment extends I2PFragmentBase {
// Container Activity must implement this interface // Container Activity must implement this interface
public interface RouterControlListener { public interface RouterControlListener {
public boolean shouldShowOnOff(); boolean shouldShowOnOff();
public boolean shouldBeOn();
public void onStartRouterClicked(); boolean shouldBeOn();
public boolean onStopRouterClicked();
/** @since 0.9.19 */ void onStartRouterClicked();
public boolean isGracefulShutdownInProgress();
/** @since 0.9.19 */ boolean onStopRouterClicked();
public boolean onGracefulShutdownClicked();
/** @since 0.9.19 */ /**
public boolean onCancelGracefulShutdownClicked(); * @since 0.9.19
*/
boolean isGracefulShutdownInProgress();
/**
* @since 0.9.19
*/
boolean onGracefulShutdownClicked();
/**
* @since 0.9.19
*/
boolean onCancelGracefulShutdownClicked();
} }
@Override @Override
@ -90,15 +123,14 @@ public class MainFragment extends I2PFragmentBase {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Init stuff here so settings work. // Init stuff here so settings work.
if(savedInstanceState != null) { if (savedInstanceState != null) {
lastRouterState = savedInstanceState.getParcelable("lastState");
String saved = savedInstanceState.getString("status"); String saved = savedInstanceState.getString("status");
if(saved != null) { if (saved != null) {
_savedStatus = saved; _savedStatus = saved;
} }
} }
_keep = true;
_handler = new Handler(); _handler = new Handler();
_updater = new Updater(); _updater = new Updater();
_oneShotUpdate = new OneShotUpdate(); _oneShotUpdate = new OneShotUpdate();
@ -109,20 +141,32 @@ public class MainFragment extends I2PFragmentBase {
Bundle savedInstanceState) { Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, container, false); View v = inflater.inflate(R.layout.fragment_main, container, false);
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights); mConsoleLights = (ImageView) v.findViewById(R.id.console_lights);
lightImage.setImageResource(R.drawable.routerlogo_0); mOnOffButton = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
vGracefulButtons = (LinearLayout) v.findViewById(R.id.router_graceful_buttons);
mScrollView = (ScrollView) v.findViewById(R.id.main_scrollview);
vStatusContainer = v.findViewById(R.id.status_container);
vNetStatusLevel = (ImageView) v.findViewById(R.id.console_net_status_level);
vNetStatusText = (TextView) v.findViewById(R.id.console_net_status_text);
vNonNetStatus = v.findViewById(R.id.console_non_net_status_container);
vUptime = (TextView) v.findViewById(R.id.console_uptime);
vActive = (TextView) v.findViewById(R.id.console_active);
vKnown = (TextView) v.findViewById(R.id.console_known);
vTunnels = (TableLayout) v.findViewById(R.id.main_tunnels);
vAdvStatus = (LinearLayout) v.findViewById(R.id.console_advanced_status);
vAdvStatusText = (TextView) v.findViewById(R.id.console_advanced_status_text);
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button); updateState(lastRouterState);
b.setOnLongClickListener(new View.OnLongClickListener() {
mOnOffButton.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) { public boolean onLongClick(View view) {
boolean on = ((ToggleButton) view).isChecked(); boolean on = ((ToggleButton) view).isChecked();
if (on) { if (on) {
_startPressed = true;
mCallback.onStartRouterClicked(); mCallback.onStartRouterClicked();
updateOneShot(); updateOneShot();
checkFirstStart(); checkFirstStart();
} else if(mCallback.onGracefulShutdownClicked()) } else if (mCallback.onGracefulShutdownClicked())
updateOneShot(); updateOneShot();
return true; return true;
} }
@ -133,7 +177,7 @@ public class MainFragment extends I2PFragmentBase {
@Override @Override
public boolean onLongClick(View view) { public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress()) if (mCallback.isGracefulShutdownInProgress())
if(mCallback.onStopRouterClicked()) if (mCallback.onStopRouterClicked())
updateOneShot(); updateOneShot();
return true; return true;
} }
@ -143,7 +187,7 @@ public class MainFragment extends I2PFragmentBase {
@Override @Override
public boolean onLongClick(View view) { public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress()) if (mCallback.isGracefulShutdownInProgress())
if(mCallback.onCancelGracefulShutdownClicked()) if (mCallback.onCancelGracefulShutdownClicked())
updateOneShot(); updateOneShot();
return true; return true;
} }
@ -157,19 +201,46 @@ public class MainFragment extends I2PFragmentBase {
super.onStart(); super.onStart();
_handler.removeCallbacks(_updater); _handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate); _handler.removeCallbacks(_oneShotUpdate);
if(_savedStatus != null) { if (_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text); TextView tv = (TextView) getActivity().findViewById(R.id.console_advanced_status_text);
tv.setText(_savedStatus); tv.setText(_savedStatus);
} }
checkDialog(); checkDialog();
_handler.postDelayed(_updater, 100); _handler.postDelayed(_updater, 100);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
lbm.sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
} }
private State lastRouterState;
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (lastRouterState == null || lastRouterState != state) {
updateState(state);
// If we have stopped, clear the status info immediately
if (Util.isStopped(state)) {
updateOneShot();
}
lastRouterState = state;
}
}
};
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
_handler.removeCallbacks(_updater); _handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate); _handler.removeCallbacks(_oneShotUpdate);
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
} }
@Override @Override
@ -180,9 +251,10 @@ public class MainFragment extends I2PFragmentBase {
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
if(_savedStatus != null) { if (lastRouterState != null)
outState.putParcelable("lastState", lastRouterState);
if (_savedStatus != null)
outState.putString("status", _savedStatus); outState.putString("status", _savedStatus);
}
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }
@ -194,7 +266,12 @@ public class MainFragment extends I2PFragmentBase {
public void run() { public void run() {
updateVisibility(); updateVisibility();
updateStatus(); try {
updateStatus();
} catch (NullPointerException npe) {
// RouterContext wasn't quite ready
Util.w("Status was updated before RouterContext was ready", npe);
}
} }
} }
@ -203,10 +280,16 @@ public class MainFragment extends I2PFragmentBase {
private int counter; private int counter;
private final int delay = 1000; private final int delay = 1000;
private final int toloop = delay / 500; private final int toloop = delay / 500;
public void run() { public void run() {
updateVisibility(); updateVisibility();
if(counter++ % toloop == 0) { if (counter++ % toloop == 0) {
updateStatus(); try {
updateStatus();
} catch (NullPointerException npe) {
// RouterContext wasn't quite ready
Util.w("Status was updated before RouterContext was ready", npe);
}
} }
//_handler.postDelayed(this, 2500); //_handler.postDelayed(this, 2500);
_handler.postDelayed(this, delay); _handler.postDelayed(this, delay);
@ -215,119 +298,91 @@ public class MainFragment extends I2PFragmentBase {
private void updateVisibility() { private void updateVisibility() {
boolean showOnOff = mCallback.shouldShowOnOff(); boolean showOnOff = mCallback.shouldShowOnOff();
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button); mOnOffButton.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
b.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
boolean isOn = mCallback.shouldBeOn(); boolean isOn = mCallback.shouldBeOn();
b.setChecked(isOn); mOnOffButton.setChecked(isOn);
boolean isGraceful = mCallback.isGracefulShutdownInProgress(); boolean isGraceful = mCallback.isGracefulShutdownInProgress();
LinearLayout gv = (LinearLayout) getActivity().findViewById(R.id.router_graceful_buttons); vGracefulButtons.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
gv.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
if (isOn && isGraceful) { if (isOn && isGraceful) {
RouterContext ctx = getRouterContext(); RouterContext ctx = getRouterContext();
if (ctx != null) { if (ctx != null) {
TextView tv = (TextView) gv.findViewById(R.id.router_graceful_status); TextView tv = (TextView) vGracefulButtons.findViewById(R.id.router_graceful_status);
long ms = ctx.router().getShutdownTimeRemaining(); long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) { if (ms > 1000) {
tv.setText(getActivity().getResources().getString(R.string.button_router_graceful, tv.setText(getActivity().getResources().getString(R.string.button_router_graceful,
DataHelper.formatDuration(ms))); DataHelper.formatDuration(ms)));
} else { } else {
tv.setText("Stopping I2P"); tv.setText(getActivity().getString(R.string.notification_status_stopping));
} }
} }
} }
if (showOnOff && !isOn) {
// Sometimes the final state message from the RouterService
// is not received. Ensure that the state image is correct.
// TODO: Fix the race between RouterService shutdown and
// IRouterState unbinding.
updateState(State.INIT);
}
}
public boolean onBackPressed() {
RouterContext ctx = getRouterContext();
// RouterService svc = _routerService; Which is better to use?!
_keep = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
Util.d("*********************************************************");
Util.d("Back pressed, Keep? " + _keep);
Util.d("*********************************************************");
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
if(!_keep) {
Thread t = new Thread(new KillMe());
t.start();
}
}
private class KillMe implements Runnable {
public void run() {
Util.d("*********************************************************");
Util.d("KillMe started!");
Util.d("*********************************************************");
try {
Thread.sleep(500); // is 500ms long enough?
} catch(InterruptedException ex) {
}
System.exit(0);
}
} }
public void updateState(State newState) { public void updateState(State newState) {
final ImageView lightImage = (ImageView) getView().findViewById(R.id.main_lights);
if (newState == State.INIT || if (newState == State.INIT ||
newState == State.STOPPED || newState == State.STOPPED ||
newState == State.MANUAL_STOPPED || newState == State.MANUAL_STOPPED ||
newState == State.MANUAL_QUITTED || newState == State.MANUAL_QUITTED ||
newState == State.NETWORK_STOPPED) { newState == State.NETWORK_STOPPED) {
lightImage.setImageResource(R.drawable.routerlogo_0); mConsoleLights.setImageResource(R.drawable.routerlogo_0);
} else if (newState == State.STARTING || } else if (newState == State.STARTING ||
newState == State.GRACEFUL_SHUTDOWN || //newState == State.GRACEFUL_SHUTDOWN || // Don't change lights for graceful
newState == State.STOPPING || newState == State.STOPPING ||
newState == State.MANUAL_STOPPING || newState == State.MANUAL_STOPPING ||
newState == State.MANUAL_QUITTING || newState == State.MANUAL_QUITTING ||
newState == State.NETWORK_STOPPING) { newState == State.NETWORK_STOPPING) {
lightImage.setImageResource(R.drawable.routerlogo_1); mConsoleLights.setImageResource(R.drawable.routerlogo_1);
} else if (newState == State.RUNNING) { } else if (newState == State.RUNNING) {
lightImage.setImageResource(R.drawable.routerlogo_2); mConsoleLights.setImageResource(R.drawable.routerlogo_2);
} else if (newState == State.ACTIVE) { } else if (newState == State.ACTIVE) {
lightImage.setImageResource(R.drawable.routerlogo_3); mConsoleLights.setImageResource(R.drawable.routerlogo_3);
} else if (newState == State.WAITING) { } else if (newState == State.WAITING) {
lightImage.setImageResource(R.drawable.routerlogo_4); mConsoleLights.setImageResource(R.drawable.routerlogo_4);
} // Ignore unknown states. } // Ignore unknown states.
} }
private void updateStatus() { private void updateStatus() {
RouterContext ctx = getRouterContext(); RouterContext ctx = getRouterContext();
ScrollView sv = (ScrollView) getActivity().findViewById(R.id.main_scrollview);
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
if(!Connectivity.isConnected(getActivity())) { if (!Connectivity.isConnected(getActivity())) {
// Manually set state, RouterService won't be running // Manually set state, RouterService won't be running
updateState(State.WAITING); updateState(State.WAITING);
vStatusText.setText("No Internet connection is available"); vNetStatusText.setText(R.string.no_internet);
vStatus.setVisibility(View.VISIBLE); vStatusContainer.setVisibility(View.VISIBLE);
sv.setVisibility(View.VISIBLE); vNonNetStatus.setVisibility(View.GONE);
} else if(ctx != null) { } else if (lastRouterState != null &&
if(_startPressed) { !Util.isStopping(lastRouterState) &&
_startPressed = false; !Util.isStopped(lastRouterState) &&
ctx != null) {
Util.NetStatus netStatus = Util.getNetStatus(getActivity(), ctx);
switch (netStatus.level) {
case ERROR:
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_error_red_24dp));
vNetStatusLevel.setVisibility(View.VISIBLE);
break;
case WARN:
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_warning_amber_24dp));
vNetStatusLevel.setVisibility(View.VISIBLE);
break;
case INFO:
default:
vNetStatusLevel.setVisibility(View.GONE);
} }
vNetStatusText.setText(getString(R.string.settings_label_network) + ": " + netStatus.status);
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
int active = ctx.commSystem().countActivePeers();
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
vUptime.setText("" + uptime);
vActive.setText("" + active);
vKnown.setText("" + known);
// Load running tunnels // Load running tunnels
loadDestinations(ctx); loadDestinations(ctx);
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) { if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) {
short reach = ctx.commSystem().getReachabilityStatus();
int active = ctx.commSystem().countActivePeers();
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
int inEx = ctx.tunnelManager().getFreeTunnelCount(); int inEx = ctx.tunnelManager().getFreeTunnelCount();
int outEx = ctx.tunnelManager().getOutboundTunnelCount(); int outEx = ctx.tunnelManager().getOutboundTunnelCount();
int inCl = ctx.tunnelManager().getInboundClientTunnelCount(); int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
@ -336,80 +391,52 @@ public class MainFragment extends I2PFragmentBase {
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue(); double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
String jobLag = DataHelper.formatDuration((long) dLag); String jobLag = DataHelper.formatDuration((long) dLag);
String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay()); String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay());
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
String netstatus;
if (reach == net.i2p.router.CommSystemFacade.STATUS_DIFFERENT) {
netstatus = "Symmetric NAT";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_HOSED) {
netstatus = "Port Failure";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_OK) {
netstatus = "OK";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_REJECT_UNSOLICITED) {
netstatus = "Firewalled";
} else {
netstatus = "Unknown";
}
String tunnelStatus = ctx.throttle().getTunnelStatus(); String tunnelStatus = ctx.throttle().getTunnelStatus();
//ctx.commSystem().getReachabilityStatus(); //ctx.commSystem().getReachabilityStatus();
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
// control total width
DecimalFormat fmt;
if(inBW >= 1000 || outBW >= 1000) {
fmt = new DecimalFormat("#0");
} else if(inBW >= 100 || outBW >= 100) {
fmt = new DecimalFormat("#0.0");
} else {
fmt = new DecimalFormat("#0.00");
}
double kBytesIn = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes() / 1024;
double kBytesOut = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes() / 1024;
// control total width
DecimalFormat kBfmt;
if(kBytesIn >= 1000 || kBytesOut >= 1000) {
kBfmt = new DecimalFormat("#0");
} else if(kBytesIn >= 100 || kBytesOut >= 100) {
kBfmt = new DecimalFormat("#0.0");
} else {
kBfmt = new DecimalFormat("#0.00");
}
String status = String status =
"Network: " + netstatus "Exploratory Tunnels in/out: " + inEx + " / " + outEx
+ "\nPeers active/known: " + active + " / " + known + "\nClient Tunnels in/out: " + inCl + " / " + outCl;
+ "\nExploratory Tunnels in/out: " + inEx + " / " + outEx
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
// Need to see if we have the participation option set to on. // Need to see if we have the participation option set to on.
// I thought there was a router method for that? I guess not! WHY NOT? // I thought there was a router method for that? I guess not! WHY NOT?
// It would be easier if we had a number to test status. // It would be easier if we had a number to test status.
String participate = "\nParticipation: " + tunnelStatus +" (" + part + ")"; String participate = "\nParticipation: " + tunnelStatus + " (" + part + ")";
String details = String details =
"\nBandwidth in/out: " + fmt.format(inBW) + " / " + fmt.format(outBW) + " KBps" "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
+ "\nData usage in/out: " + kBfmt.format(kBytesIn) + " / " + kBfmt.format(kBytesOut) + " KB"
+ "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B' + "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
+ "\nJob Lag: " + jobLag + "\nJob Lag: " + jobLag
+ "\nMsg Delay: " + msgDelay + "\nMsg Delay: " + msgDelay;
+ "\nUptime: " + uptime;
_savedStatus = status + participate + details; _savedStatus = status + participate + details;
vStatusText.setText(_savedStatus); vAdvStatusText.setText(_savedStatus);
vStatus.setVisibility(View.VISIBLE); vAdvStatus.setVisibility(View.VISIBLE);
} else { } else {
vStatus.setVisibility(View.GONE); vAdvStatus.setVisibility(View.GONE);
} }
sv.setVisibility(View.VISIBLE); vStatusContainer.setVisibility(View.VISIBLE);
vNonNetStatus.setVisibility(View.VISIBLE);
// Usage stats in bottom toolbar
double inBw = ctx.bandwidthLimiter().getReceiveBps();
double outBw = ctx.bandwidthLimiter().getSendBps();
double inData = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes();
double outData = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes();
((TextView) getActivity().findViewById(R.id.console_download_stats)).setText(
Util.formatSize(inBw) + "Bps / " + Util.formatSize(inData) + "B");
((TextView) getActivity().findViewById(R.id.console_upload_stats)).setText(
Util.formatSize(outBw) + "Bps / " + Util.formatSize(outData) + "B");
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.VISIBLE);
} else { } else {
// network but no router context // network but no router context
vStatusText.setText("Not running"); vStatusContainer.setVisibility(View.GONE);
sv.setVisibility(View.INVISIBLE); getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.INVISIBLE);
/** /**
* ** * **
* RouterService svc = _routerService; String status = "connected? " * RouterService svc = _routerService; String status = "connected? "
@ -423,21 +450,24 @@ public class MainFragment extends I2PFragmentBase {
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ? * "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
* "null" : svc.canManualStop()); tv.setText(status); * "null" : svc.canManualStop()); tv.setText(status);
* tv.setVisibility(View.VISIBLE); * tv.setVisibility(View.VISIBLE);
*** ***
*/ */
} }
} }
/** /**
* Based on net.i2p.router.web.SummaryHelper.getDestinations() * Based on net.i2p.router.web.SummaryHelper.getDestinations()
*
* @param ctx The RouterContext * @param ctx The RouterContext
*/ */
private void loadDestinations(RouterContext ctx) { private void loadDestinations(RouterContext ctx) {
TableLayout dests = (TableLayout) getView().findViewById(R.id.main_tunnels); vTunnels.removeAllViews();
dests.removeAllViews();
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients()); List<Destination> clients = null;
if (!clients.isEmpty()) { if (ctx.clientManager() != null)
clients = new ArrayList<Destination>(ctx.clientManager().listClients());
if (clients != null && !clients.isEmpty()) {
Collections.sort(clients, new AlphaComparator(ctx)); Collections.sort(clients, new AlphaComparator(ctx));
for (Destination client : clients) { for (Destination client : clients) {
String name = getName(ctx, client); String name = getName(ctx, client);
@ -479,25 +509,29 @@ public class MainFragment extends I2PFragmentBase {
type.setBackgroundResource(R.drawable.tunnel_yellow); type.setBackgroundResource(R.drawable.tunnel_yellow);
} }
dests.addView(dest); vTunnels.addView(dest);
} }
} else { } else {
TableRow empty = new TableRow(getActivity()); TableRow empty = new TableRow(getActivity());
TextView emptyText = new TextView(getActivity()); TextView emptyText = new TextView(getActivity());
emptyText.setText(R.string.no_client_tunnels_running); emptyText.setText(R.string.no_tunnels_running);
empty.addView(emptyText); empty.addView(emptyText);
dests.addView(empty); vTunnels.addView(empty);
} }
} }
/** compare translated nicknames - put "shared clients" first in the sort */ private static final String SHARED_CLIENTS = "shared clients";
/**
* compare translated nicknames - put "shared clients" first in the sort
*/
private class AlphaComparator implements Comparator<Destination> { private class AlphaComparator implements Comparator<Destination> {
private String xsc; private String xsc;
private RouterContext _ctx; private RouterContext _ctx;
public AlphaComparator(RouterContext ctx) { public AlphaComparator(RouterContext ctx) {
_ctx = ctx; _ctx = ctx;
xsc = _(ctx, "shared clients"); xsc = _(ctx, SHARED_CLIENTS);
} }
public int compare(Destination lhs, Destination rhs) { public int compare(Destination lhs, Destination rhs) {
@ -511,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) { private String getName(RouterContext ctx, Destination d) {
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash()); TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
String name = (in != null ? in.getDestinationNickname() : null); String name = (in != null ? in.getDestinationNickname() : null);
if (name == null) { if (name == null) {
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash()); TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
name = (out != null ? out.getDestinationNickname() : null); name = (out != null ? out.getDestinationNickname() : null);
if (name == null)
name = d.calculateHash().toBase64().substring(0,6);
else
name = _(ctx, name);
} else {
name = _(ctx, name);
} }
if (name == null)
name = d.calculateHash().toBase64().substring(0, 6);
else
name = _(ctx, name);
return name; return name;
} }
private String _(RouterContext ctx, String s) { 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() { private void checkDialog() {
I2PActivityBase ab = (I2PActivityBase) getActivity(); final I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true); String language = PreferenceManager.getDefaultSharedPreferences(ab).getString(
if (configureBrowser) { getString(R.string.PREF_LANGUAGE), null
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog(); );
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser"); if (language == null) {
ab.setPref(PREF_CONFIGURE_BROWSER, false); AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
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(); /*VersionDialog dialog = new VersionDialog();
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??"); String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");

View File

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

View File

@ -1,370 +1,154 @@
package net.i2p.android.router; package net.i2p.android.router;
import android.annotation.TargetApi; import android.content.Intent;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.support.v4.app.Fragment;
import android.preference.PreferenceCategory; import android.support.v4.app.FragmentManager;
import android.preference.PreferenceFragment; import android.support.v4.content.LocalBroadcastManager;
import android.preference.PreferenceManager; import android.support.v4.preference.PreferenceFragment;
import android.preference.PreferenceScreen; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; 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.I2PActivity;
import net.i2p.android.router.service.StatSummarizer; import net.i2p.android.preferences.AdvancedPreferenceFragment;
import net.i2p.android.router.util.PortPreference; import net.i2p.android.preferences.AppearancePreferenceFragment;
import net.i2p.android.router.util.Util; import net.i2p.android.preferences.GraphsPreferenceFragment;
import net.i2p.router.RouterContext; import net.i2p.android.preferences.LoggingPreferenceFragment;
import net.i2p.stat.FrequencyStat; import net.i2p.android.preferences.NetworkPreferenceFragment;
import net.i2p.stat.Rate; import net.i2p.android.router.addressbook.AddressbookSettingsActivity;
import net.i2p.stat.RateStat; import net.i2p.android.router.service.RouterService;
import net.i2p.stat.StatManager; import net.i2p.android.util.LocaleManager;
import net.i2p.util.LogManager;
import java.util.List; public class SettingsActivity extends AppCompatActivity implements
import java.util.Map; SharedPreferences.OnSharedPreferenceChangeListener {
import java.util.Properties; public static final String PREFERENCE_CATEGORY = "preference_category";
import java.util.Set; public static final String PREFERENCE_CATEGORY_NETWORK = "preference_category_network";
import java.util.SortedSet; public static final String PREFERENCE_CATEGORY_GRAPHS = "preference_category_graphs";
public static final String PREFERENCE_CATEGORY_LOGGING = "preference_category_logging";
public static final String PREFERENCE_CATEGORY_ADDRESSBOOK = "preference_category_addressbook";
public static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
public static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
public class SettingsActivity extends PreferenceActivity { private final LocaleManager localeManager = new LocaleManager();
// Actions for legacy settings
private static final String ACTION_PREFS_NET = "net.i2p.android.router.PREFS_NET";
public static final String ACTION_PREFS_GRAPHS = "net.i2p.android.router.PREFS_GRAPHS";
private static final String ACTION_PREFS_LOGGING = "net.i2p.android.router.PREFS_LOGGING";
private static final String ACTION_PREFS_ADVANCED = "net.i2p.android.router.PREFS_ADVANCED";
private Toolbar mToolbar;
@SuppressWarnings("deprecation")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
localeManager.onCreate(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);
String action = getIntent().getAction(); Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
if (action != null) { setSupportActionBar(toolbar);
switch (action) { getSupportActionBar().setDisplayHomeAsUpEnabled(true);
case ACTION_PREFS_NET:
addPreferencesFromResource(R.xml.settings_net);
break;
case ACTION_PREFS_GRAPHS:
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(this, getPreferenceScreen(), Util.getRouterContext());
break;
case ACTION_PREFS_LOGGING:
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
break;
case ACTION_PREFS_ADVANCED:
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(this, getPreferenceScreen());
break;
}
} else {
// Load any properties that the router might have changed on us.
setupPreferences(this, Util.getRouterContext());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { Fragment fragment;
// Load the legacy preferences headers String category = getIntent().getStringExtra(PREFERENCE_CATEGORY);
addPreferencesFromResource(R.xml.settings_headers_legacy); if (category != null)
} fragment = getFragmentForCategory(category);
} else
fragment = new SettingsFragment();
mToolbar.setTitle(getTitle()); getSupportFragmentManager().beginTransaction()
} .replace(R.id.fragment, fragment)
.commit();
protected static void setupPreferences(Context context, RouterContext ctx) {
if (ctx != null) {
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
int udpPort = ctx.getProperty(udpPortKey, -1);
int ntcpPort = ctx.getProperty(ntcpPortKey, -1);
boolean ntcpAutoPort = ctx.getBooleanPropertyDefaultTrue(ntcpAutoPortKey);
if (ntcpPort < 0 && ntcpAutoPort)
ntcpPort = udpPort;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getInt(udpPortKey, -1) != udpPort ||
prefs.getInt(ntcpPortKey, -1) != ntcpPort) {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(udpPortKey, udpPort);
editor.putInt(ntcpPortKey, ntcpPort);
// commit() instead of apply() because this needs to happen
// before SettingsActivity loads its Preferences.
editor.commit();
}
}
}
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
if (ctx == null) {
PreferenceCategory noRouter = new PreferenceCategory(context);
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
} else if (StatSummarizer.instance() == null) {
PreferenceCategory noStats = new PreferenceCategory(context);
noStats.setTitle(R.string.stats_not_ready);
ps.addPreference(noStats);
} else {
StatManager mgr = ctx.statManager();
Map<String, SortedSet<String>> all = mgr.getStatsByGroup();
for (String group : all.keySet()) {
SortedSet<String> stats = all.get(group);
if (stats.size() == 0) continue;
PreferenceCategory groupPrefs = new PreferenceCategory(context);
groupPrefs.setKey("stat.groups." + group);
groupPrefs.setTitle(group);
ps.addPreference(groupPrefs);
for (String stat : stats) {
String key;
String description;
boolean canBeGraphed = false;
boolean currentIsGraphed = false;
RateStat rs = mgr.getRate(stat);
if (rs != null) {
description = rs.getDescription();
long period = rs.getPeriods()[0]; // should be the minimum
key = stat + "." + period;
if (period <= 10*60*1000) {
Rate r = rs.getRate(period);
canBeGraphed = r != null;
if (canBeGraphed) {
currentIsGraphed = r.getSummaryListener() != null;
}
}
} else {
FrequencyStat fs = mgr.getFrequency(stat);
if (fs != null) {
key = stat;
description = fs.getDescription();
// FrequencyStats cannot be graphed, but can be logged.
// XXX: Should log settings be here as well, or in a
// separate settings menu?
} else {
Util.e("Stat does not exist?! [" + stat + "]");
continue;
}
}
CheckBoxPreference statPref = new CheckBoxPreference(context);
statPref.setKey("stat.summaries." + key);
statPref.setTitle(stat);
statPref.setSummary(description);
statPref.setEnabled(canBeGraphed);
statPref.setChecked(currentIsGraphed);
groupPrefs.addPreference(statPref);
}
}
}
}
protected static void setupLoggingSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
if (ctx != null) {
LogManager mgr = ctx.logManager();
// Log level overrides
/*
StringBuilder buf = new StringBuilder(32*1024);
Properties limits = mgr.getLimits();
TreeSet<String> sortedLogs = new TreeSet<String>();
for (Iterator iter = limits.keySet().iterator(); iter.hasNext(); ) {
String prefix = (String)iter.next();
sortedLogs.add(prefix);
}
for (Iterator iter = sortedLogs.iterator(); iter.hasNext(); ) {
String prefix = (String)iter.next();
String level = limits.getProperty(prefix);
buf.append(prefix).append('=').append(level).append('\n');
}
*/
/* Don't show, there are no settings that require the router
} else {
PreferenceCategory noRouter = new PreferenceCategory(context);
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
*/
}
}
protected static void setupAdvancedSettings(final Context context, PreferenceScreen ps) {
final String udpEnableKey = context.getString(R.string.PROP_ENABLE_UDP);
final String ntcpEnableKey = context.getString(R.string.PROP_ENABLE_NTCP);
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
final CheckBoxPreference udpEnable = (CheckBoxPreference) ps.findPreference(udpEnableKey);
final CheckBoxPreference ntcpEnable = (CheckBoxPreference) ps.findPreference(ntcpEnableKey);
final PortPreference udpPort = (PortPreference) ps.findPreference(udpPortKey);
final PortPreference ntcpPort = (PortPreference) ps.findPreference(ntcpPortKey);
final CheckBoxPreference ntcpAutoPort = (CheckBoxPreference) ps.findPreference(ntcpAutoPortKey);
udpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked || ntcpEnable.isChecked())
return true;
else {
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
return false;
}
}
});
ntcpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked || udpEnable.isChecked())
return true;
else {
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
return false;
}
}
});
udpPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (ntcpAutoPort.isChecked())
ntcpPort.setText((String) newValue);
return true;
}
});
ntcpAutoPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked)
ntcpPort.setText(udpPort.getText());
return true;
}
});
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
// The resource com.android.internal.R.bool.preferences_prefer_dual_pane
// has different definitions based upon screen size. At present, it will
// be true for -sw720dp devices, false otherwise. For your curiosity, in
// Nexus 7 it is false.
loadHeadersFromResource(R.xml.settings_headers, target);
} }
@Override @Override
public void setContentView(int layoutResID) { public void onResume() {
ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate( super.onResume();
R.layout.activity_settings, localeManager.onResume(this);
(ViewGroup) getWindow().getDecorView().getRootView(), false);
mToolbar = (Toolbar) contentView.findViewById(R.id.main_toolbar);
mToolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
ViewGroup contentWrapper = (ViewGroup) contentView.findViewById(R.id.content_wrapper);
LayoutInflater.from(this).inflate(layoutResID, contentWrapper, true);
getWindow().setContentView(contentView);
} }
@Override @Override
protected void onPause() { public boolean onSupportNavigateUp() {
List<Properties> lProps = Util.getPropertiesFromPreferences(this); FragmentManager fragmentManager = getSupportFragmentManager();
Properties props = lProps.get(0); if (fragmentManager.getBackStackEntryCount() > 0) {
Properties propsToRemove = lProps.get(1); fragmentManager.popBackStack();
Properties logSettings = lProps.get(2);
Set toRemove = propsToRemove.keySet();
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props, toRemove);
// Apply new config if we are running.
RouterContext rCtx = Util.getRouterContext();
if (rCtx != null) {
rCtx.router().saveConfig(props, toRemove);
// Merge in new log settings
saveLoggingChanges(rCtx, logSettings);
} else { } else {
// Merge in new config settings, write the file. Intent intent = new Intent(this, I2PActivity.class);
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props, toRemove); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
// Merge in new log settings finish();
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
} }
return true;
// Store the settings in Android
super.onPause();
if (restartRequired)
Toast.makeText(this, R.string.settings_router_restart_required, Toast.LENGTH_LONG).show();
} }
private void saveLoggingChanges(I2PAppContext ctx, Properties logSettings) { @Override
boolean shouldSave = false; public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getResources().getString(R.string.PREF_LANGUAGE))) {
for (Object key : logSettings.keySet()) { localeManager.onResume(this);
if ("logger.defaultLevel".equals(key)) { Intent intent = new Intent(RouterService.LOCAL_BROADCAST_LOCALE_CHANGED);
String defaultLevel = (String) logSettings.get(key); LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
String oldDefault = ctx.logManager().getDefaultLimit();
if (!defaultLevel.equals(oldDefault)) {
shouldSave = true;
ctx.logManager().setDefaultLimit(defaultLevel);
}
}
}
if (shouldSave) {
ctx.logManager().saveConfig();
} }
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class SettingsFragment extends PreferenceFragment { public static class SettingsFragment extends PreferenceFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle paramBundle) {
super.onCreate(savedInstanceState); super.onCreate(paramBundle);
addPreferencesFromResource(R.xml.settings);
String settings = getArguments().getString("settings"); this.findPreference(PREFERENCE_CATEGORY_NETWORK)
switch (settings) { .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NETWORK));
case "net": this.findPreference(PREFERENCE_CATEGORY_GRAPHS)
addPreferencesFromResource(R.xml.settings_net); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_GRAPHS));
break; this.findPreference(PREFERENCE_CATEGORY_LOGGING)
case "graphs": .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LOGGING));
addPreferencesFromResource(R.xml.settings_graphs); this.findPreference(PREFERENCE_CATEGORY_ADDRESSBOOK)
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext()); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADDRESSBOOK));
break; this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
case "logging": .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
addPreferencesFromResource(R.xml.settings_logging); this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext()); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
break; }
case "advanced":
addPreferencesFromResource(R.xml.settings_advanced); @Override
setupAdvancedSettings(getActivity(), getPreferenceScreen()); public void onResume() {
break; super.onResume();
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings);
}
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
private String category;
public CategoryClickListener(String category) {
this.category = category;
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (PREFERENCE_CATEGORY_ADDRESSBOOK.equals(category)) {
Intent i = new Intent(getActivity(), AddressbookSettingsActivity.class);
startActivity(i);
return true;
}
Fragment fragment = getFragmentForCategory(category);
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment, fragment)
.addToBackStack(null)
.commit();
return true;
} }
} }
} }
@Override private static Fragment getFragmentForCategory(String category) {
protected boolean isValidFragment(String fragmentName) { switch (category) {
return SettingsFragment.class.getName().equals(fragmentName); case PREFERENCE_CATEGORY_NETWORK:
return new NetworkPreferenceFragment();
case PREFERENCE_CATEGORY_GRAPHS:
return new GraphsPreferenceFragment();
case PREFERENCE_CATEGORY_LOGGING:
return new LoggingPreferenceFragment();
case PREFERENCE_CATEGORY_APPEARANCE:
return new AppearancePreferenceFragment();
case PREFERENCE_CATEGORY_ADVANCED:
return new AdvancedPreferenceFragment();
default:
throw new AssertionError();
}
} }
} }

View File

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

View File

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

View File

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

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,13 +1,14 @@
package net.i2p.android.router.addressbook; package net.i2p.android.router.addressbook;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.wizard.model.AbstractWizardModel; import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity; import net.i2p.android.wizard.ui.AbstractWizardActivity;
@ -31,7 +32,7 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent(); Intent result = new Intent();
setResult(Activity.RESULT_OK, result); setResult(Activity.RESULT_OK, result);
result.putExtra(AddressbookFragment.ADD_WIZARD_DATA, mWizardModel.save()); result.putExtra(AddressbookContainer.ADD_WIZARD_DATA, mWizardModel.save());
dialog.dismiss(); dialog.dismiss();
finish(); finish();
} }

View File

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

View File

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

View File

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

View File

@ -1,38 +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.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.router.R;
public class ConfigureBrowserDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_browser_title)
.setMessage(R.string.configure_browser_for_i2p)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
});
return b.create();
}
}

View File

@ -1,10 +1,11 @@
package net.i2p.android.router.dialog; package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.util.Linkify; import android.text.util.Linkify;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -27,7 +28,13 @@ public class FirstStartDialog extends DialogFragment {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity()); AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.first_start_title) b.setTitle(R.string.first_start_title)
.setView(view); .setView(view)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
}
});
return b.create(); return b.create();
} }
} }

View File

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

View File

@ -1,13 +1,13 @@
package net.i2p.android.router.dialog; package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.router.I2PActivityBase; import net.i2p.android.I2PActivityBase;
import net.i2p.android.router.MainFragment; import net.i2p.android.router.MainFragment;
import net.i2p.android.router.R; import net.i2p.android.router.R;
import net.i2p.android.router.util.Util; import net.i2p.android.router.util.Util;

View File

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

View File

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

View File

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

@ -24,6 +24,7 @@ import java.util.List;
public class LogFragment extends ListFragment implements public class LogFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<String>> { LoaderManager.LoaderCallbacks<List<String>> {
public static final String LOG_LEVEL = "log_level"; 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 * The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets. * activated item position. Only used on tablets.
@ -48,7 +49,7 @@ public class LogFragment extends ListFragment implements
// Container Activity must implement this interface // Container Activity must implement this interface
public interface OnEntrySelectedListener { public interface OnEntrySelectedListener {
public void onEntrySelected(String entry); void onEntrySelected(String entry);
} }
public static LogFragment newInstance(String level) { public static LogFragment newInstance(String level) {
@ -113,11 +114,11 @@ public class LogFragment extends ListFragment implements
I2PAppContext ctx = I2PAppContext.getCurrentContext(); I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null) { if (ctx != null) {
setEmptyText("ERROR".equals(mLogLevel) ? setEmptyText(getString(LOG_LEVEL_ERROR.equals(mLogLevel) ?
"No error messages" : "No messages"); R.string.no_error_messages : R.string.no_messages));
setListShown(false); setListShown(false);
getLoaderManager().initLoader("ERROR".equals(mLogLevel) ? getLoaderManager().initLoader(LOG_LEVEL_ERROR.equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL, null, this); LEVEL_ERROR : LEVEL_ALL, null, this);
} else } else
setEmptyText(getResources().getString( setEmptyText(getResources().getString(
@ -163,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) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(logText); clipboard.setText(logText);
@ -205,20 +206,12 @@ public class LogFragment extends ListFragment implements
mActivatedPosition = position; mActivatedPosition = position;
} }
/** fixme plurals */ private static String getHeader(Context ctx, int sz, boolean errorsOnly) {
private static String getHeader(int sz, boolean errorsOnly) { if (sz > 0)
if (errorsOnly) { return ctx.getResources().getQuantityString(errorsOnly ?
if (sz == 0) R.plurals.log_error_messages : R.plurals.log_messages, sz, sz);
return "No error messages"; else
if (sz == 1) return ctx.getString(errorsOnly ? R.string.no_error_messages : R.string.no_messages);
return "1 error message";
return sz + " error messages, newest first";
}
if (sz == 0)
return "No messages";
if (sz == 1)
return "1 message";
return sz + " messages, newest first";
} }
// LoaderManager.LoaderCallbacks<List<String>> // LoaderManager.LoaderCallbacks<List<String>>
@ -230,14 +223,14 @@ public class LogFragment extends ListFragment implements
public void onLoadFinished(Loader<List<String>> loader, public void onLoadFinished(Loader<List<String>> loader,
List<String> data) { List<String> data) {
if (loader.getId() == ("ERROR".equals(mLogLevel) ? if (loader.getId() == (LOG_LEVEL_ERROR.equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL)) { LEVEL_ERROR : LEVEL_ALL)) {
synchronized (mLogEntries) { synchronized (mLogEntries) {
mLogEntries.clear(); mLogEntries.clear();
mLogEntries.addAll(data); mLogEntries.addAll(data);
} }
mAdapter.setData(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); mHeaderView.setText(header);
if (isResumed()) { if (isResumed()) {
@ -249,7 +242,7 @@ public class LogFragment extends ListFragment implements
} }
public void onLoaderReset(Loader<List<String>> loader) { 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)) { LEVEL_ERROR : LEVEL_ALL)) {
mAdapter.setData(null); mAdapter.setData(null);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -11,16 +11,13 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ListView; 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.R;
import net.i2p.android.router.util.Util;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import java.util.List; import java.util.List;
public class NetDbListFragment extends ListFragment implements public class NetDbListFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
LoaderManager.LoaderCallbacks<List<NetDbEntry>> { LoaderManager.LoaderCallbacks<List<NetDbEntry>> {
public static final String SHOW_ROUTERS = "show_routers"; 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 static final String STATE_ACTIVATED_POSITION = "activated_position";
private boolean mOnActivityCreated;
private RouterContextProvider mRouterContextProvider;
private OnEntrySelectedListener mEntrySelectedCallback; private OnEntrySelectedListener mEntrySelectedCallback;
private NetDbEntryAdapter mAdapter; private NetDbEntryAdapter mAdapter;
private boolean mRouters; private boolean mRouters;
@ -45,22 +40,13 @@ public class NetDbListFragment extends ListFragment implements
// Container Activity must implement this interface // Container Activity must implement this interface
public interface OnEntrySelectedListener { public interface OnEntrySelectedListener {
public void onEntrySelected(boolean isRouterInfo, Hash entryHash); void onEntrySelected(boolean isRouterInfo, Hash entryHash);
} }
@Override @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(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 // This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception // the callback interface. If not, it throws an exception
try { try {
@ -104,22 +90,18 @@ public class NetDbListFragment extends ListFragment implements
setListAdapter(mAdapter); setListAdapter(mAdapter);
mOnActivityCreated = true; if (Util.getRouterContext() == null)
if (getRouterContext() != null)
onRouterConnectionReady();
else
setEmptyText(getResources().getString( setEmptyText(getResources().getString(
R.string.router_not_running)); R.string.router_not_running));
} else {
setEmptyText(getResources().getString((mRouters ?
public void onRouterConnectionReady() { R.string.netdb_routers_empty :
setEmptyText(getResources().getString((mRouters ?
R.string.netdb_routers_empty :
R.string.netdb_leases_empty))); R.string.netdb_leases_empty)));
setListShown(false); setListShown(false);
getLoaderManager().initLoader(mRouters ? ROUTER_LOADER_ID getLoaderManager().initLoader(mRouters ? ROUTER_LOADER_ID
: LEASESET_LOADER_ID, null, this); : LEASESET_LOADER_ID, null, this);
}
} }
@Override @Override
@ -149,7 +131,7 @@ public class NetDbListFragment extends ListFragment implements
// Handle presses on the action bar items // Handle presses on the action bar items
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_refresh: case R.id.action_refresh:
if (getRouterContext() != null) { if (Util.getRouterContext() != null) {
setListShown(false); setListShown(false);
getLoaderManager().restartLoader(mRouters ? ROUTER_LOADER_ID getLoaderManager().restartLoader(mRouters ? ROUTER_LOADER_ID
: LEASESET_LOADER_ID, null, this); : LEASESET_LOADER_ID, null, this);
@ -178,23 +160,10 @@ public class NetDbListFragment extends ListFragment implements
mActivatedPosition = position; 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>> // LoaderManager.LoaderCallbacks<List<NetDbEntry>>
public Loader<List<NetDbEntry>> onCreateLoader(int id, Bundle args) { public Loader<List<NetDbEntry>> onCreateLoader(int id, Bundle args) {
return new NetDbEntryLoader(getActivity(), return new NetDbEntryLoader(getActivity(), mRouters);
getRouterContext(), mRouters);
} }
public void onLoadFinished(Loader<List<NetDbEntry>> loader, public void onLoadFinished(Loader<List<NetDbEntry>> loader,

View File

@ -3,6 +3,7 @@ package net.i2p.android.router.netdb;
import android.content.Context; import android.content.Context;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.R;
import net.i2p.data.Hash; import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo; import net.i2p.data.router.RouterInfo;
@ -59,7 +60,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
} }
ret.add(versions); ret.add(versions);
ret.add(countries); //ret.add(countries);
ret.add(transports); ret.add(transports);
return ret; return ret;
@ -69,14 +70,28 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
private static final int SSUI = 2; private static final int SSUI = 2;
private static final int NTCP = 4; private static final int NTCP = 4;
private static final int IPV6 = 8; private static final int IPV6 = 8;
private static final String[] TNAMES = { "Hidden or starting up", "SSU", "SSU with introducers", "", private static final int[] TNAMES = {
"NTCP", "NTCP and SSU", "NTCP and SSU with introducers", "", R.string.tname_0,
"", "IPv6 SSU", "IPv6 Only SSU, introducers", "IPv6 SSU, introducers", R.string.tname_1,
"IPv6 NTCP", "IPv6 NTCP, SSU", "IPv6 Only NTCP, SSU, introducers", "IPv6 NTCP, SSU, introducers" }; 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 * what transport types
*/ */
private static String classifyTransports(RouterInfo info) { private String classifyTransports(RouterInfo info) {
int rv = 0; int rv = 0;
for (RouterAddress addr : info.getAddresses()) { for (RouterAddress addr : info.getAddresses()) {
String style = addr.getTransportStyle(); String style = addr.getTransportStyle();
@ -93,7 +108,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
rv |= IPV6; rv |= IPV6;
} }
return TNAMES[rv]; return getContext().getString(TNAMES[rv]);
} }
@Override @Override

View File

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

View File

@ -51,7 +51,7 @@ public class NetDbSummaryTableFragment extends Fragment {
switch (mCategory) { switch (mCategory) {
case 1: case 1:
case 2: //case 2:
Collections.sort(objects); Collections.sort(objects);
break; break;
default: default:
@ -83,16 +83,16 @@ public class NetDbSummaryTableFragment extends Fragment {
switch (mCategory) { switch (mCategory) {
case 1: case 1:
tl1.setText("Transports"); // tl1.setText(R.string.country);
break; // break;
case 2: //case 2:
tl1.setText("Country"); tl1.setText(R.string.transport);
break; break;
default: default:
tl1.setText("Version"); tl1.setText(R.string.version);
break; break;
} }
tl2.setText("Count"); tl2.setText(R.string.count);
titleRow.addView(tl1); titleRow.addView(tl1);
titleRow.addView(tl2); titleRow.addView(tl2);

View File

@ -9,14 +9,10 @@ import java.io.File;
class Init { class Init {
private final Context ctx;
private final String myDir; private final String myDir;
private final String _ourVersion;
public Init(Context c) { public Init(Context c) {
ctx = c;
myDir = c.getFilesDir().getAbsolutePath(); myDir = c.getFilesDir().getAbsolutePath();
_ourVersion = Util.getOurVersion(c);
} }
void initialize() { void initialize() {

View File

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

@ -18,6 +18,7 @@ import net.i2p.android.router.receiver.I2PReceiver;
import net.i2p.android.router.util.Connectivity; import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Notifications; import net.i2p.android.router.util.Notifications;
import net.i2p.android.router.util.Util; import net.i2p.android.router.util.Util;
import net.i2p.android.util.LocaleManager;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.router.Job; import net.i2p.router.Job;
import net.i2p.router.Router; import net.i2p.router.Router;
@ -47,6 +48,12 @@ public class RouterService extends Service {
*/ */
public static final String LOCAL_BROADCAST_STATE_CHANGED = "net.i2p.android.LOCAL_BROADCAST_STATE_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"; 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 RouterContext _context;
private String _myDir; private String _myDir;
@ -60,7 +67,6 @@ public class RouterService extends Service {
private final Object _stateLock = new Object(); private final Object _stateLock = new Object();
private Handler _handler; private Handler _handler;
private Runnable _updater; private Runnable _updater;
private boolean mStartCalled;
private static final String SHARED_PREFS = "net.i2p.android.router"; private static final String SHARED_PREFS = "net.i2p.android.router";
private static final String LAST_STATE = "service.lastState"; private static final String LAST_STATE = "service.lastState";
private static final String EXTRA_RESTART = "restart"; private static final String EXTRA_RESTART = "restart";
@ -76,7 +82,6 @@ public class RouterService extends Service {
@Override @Override
public void onCreate() { public void onCreate() {
mStartCalled = false;
State lastState = getSavedState(); State lastState = getSavedState();
setState(State.INIT); setState(State.INIT);
Util.d(this + " onCreate called" Util.d(this + " onCreate called"
@ -96,8 +101,9 @@ public class RouterService extends Service {
_binder = new RouterBinder(this); _binder = new RouterBinder(this);
_handler = new Handler(); _handler = new Handler();
_updater = new Updater(); _updater = new Updater();
LocalBroadcastManager.getInstance(this).registerReceiver(onStateRequested, LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
new IntentFilter(LOCAL_BROADCAST_REQUEST_STATE)); 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) { if(lastState == State.RUNNING || lastState == State.ACTIVE) {
Intent intent = new Intent(this, RouterService.class); Intent intent = new Intent(this, RouterService.class);
intent.putExtra(EXTRA_RESTART, true); intent.putExtra(EXTRA_RESTART, true);
@ -120,6 +126,13 @@ public class RouterService extends Service {
} }
}; };
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 * NOT called by system if it restarts us after a crash
*/ */
@ -130,7 +143,6 @@ public class RouterService extends Service {
+ " Flags is: " + flags + " Flags is: " + flags
+ " ID is: " + startId + " ID is: " + startId
+ " Current state is: " + _state); + " Current state is: " + _state);
mStartCalled = true;
boolean restart = intent != null && intent.getBooleanExtra(EXTRA_RESTART, false); boolean restart = intent != null && intent.getBooleanExtra(EXTRA_RESTART, false);
if(restart) { if(restart) {
Util.d(this + " RESTARTING"); Util.d(this + " RESTARTING");
@ -143,15 +155,15 @@ public class RouterService extends Service {
_receiver = new I2PReceiver(this); _receiver = new I2PReceiver(this);
if(Connectivity.isConnected(this)) { if(Connectivity.isConnected(this)) {
if(restart) { if(restart) {
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting"); _statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_restarting);
} else { } else {
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up"); _statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_starting);
} }
setState(State.STARTING); setState(State.STARTING);
_starterThread = new Thread(new Starter()); _starterThread = new Thread(new Starter());
_starterThread.start(); _starterThread.start();
} else { } 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); setState(State.WAITING);
_handler.postDelayed(new Waiter(), 10 * 1000); _handler.postDelayed(new Waiter(), 10 * 1000);
} }
@ -180,7 +192,7 @@ public class RouterService extends Service {
if(_state != State.WAITING) { if(_state != State.WAITING) {
return; 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); setState(State.STARTING);
_starterThread = new Thread(new Starter()); _starterThread = new Thread(new Starter());
_starterThread.start(); _starterThread.start();
@ -208,13 +220,13 @@ public class RouterService extends Service {
return; return;
} }
setState(State.RUNNING); setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running"); _statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_running);
_context = Util.getRouterContext(); _context = Util.getRouterContext();
if (_context == null) { if (_context == null) {
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down."); throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
} }
_context.router().setKillVMOnEnd(false); _context.router().setKillVMOnEnd(false);
Job loadJob = new LoadClientsJob(_context, _notif); Job loadJob = new LoadClientsJob(RouterService.this, _context, _notif);
_context.jobQueue().addJob(loadJob); _context.jobQueue().addJob(loadJob);
_context.addShutdownTask(new ShutdownHook()); _context.addShutdownTask(new ShutdownHook());
_context.addFinalShutdownTask(new FinalShutdownHook()); _context.addFinalShutdownTask(new FinalShutdownHook());
@ -261,9 +273,10 @@ public class RouterService extends Service {
} }
String text = String text =
getResources().getString(R.string.notification_status_bw, getResources().getString(R.string.notification_status_text,
fmt.format(inBW), fmt.format(outBW)); Util.formatSize(inBW), Util.formatSize(outBW));
// TODO change string resource after 0.9.20 to use Util.formatSize()
String bigText = String bigText =
getResources().getString(R.string.notification_status_bw, getResources().getString(R.string.notification_status_bw,
fmt.format(inBW), fmt.format(outBW)) + '\n' fmt.format(inBW), fmt.format(outBW)) + '\n'
@ -278,23 +291,23 @@ public class RouterService extends Service {
if (isGracefulShutdownInProgress()) { if (isGracefulShutdownInProgress()) {
long ms = ctx.router().getShutdownTimeRemaining(); long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) { if (ms > 1000) {
_currTitle = "Stopping I2P in " + DataHelper.formatDuration(ms); _currTitle = getString(R.string.notification_status_graceful, DataHelper.formatDuration(ms));
} else { } else {
_currTitle = "Stopping I2P"; _currTitle = getString(R.string.notification_status_stopping);
} }
} else if (haveTunnels != _hadTunnels) { } else if (haveTunnels != _hadTunnels) {
if(haveTunnels) { if(haveTunnels) {
_currTitle = "Client tunnels are ready"; _currTitle = getString(R.string.notification_status_client_ready);
setState(State.ACTIVE); setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle); _statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle);
} else { } else {
_currTitle = "Client tunnels are down"; _currTitle = getString(R.string.notification_status_client_down);
setState(State.RUNNING); setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, _currTitle); _statusBar.replace(StatusBar.ICON_RUNNING, _currTitle);
} }
_hadTunnels = haveTunnels; _hadTunnels = haveTunnels;
} else if (_currTitle == null || _currTitle.equals("")) } else if (_currTitle == null || _currTitle.equals(""))
_currTitle = "I2P is running"; _currTitle = getString(R.string.notification_status_running);
_statusBar.update(_currTitle, text, bigText); _statusBar.update(_currTitle, text, bigText);
} }
@ -334,7 +347,10 @@ public class RouterService extends Service {
} }
public boolean isStarted() throws RemoteException { 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 { public State getState() throws RemoteException {
@ -379,7 +395,7 @@ public class RouterService extends Service {
} }
public boolean canManualStop() { public boolean canManualStop() {
return _state == State.WAITING || _state == State.STARTING || return _state == State.WAITING ||
_state == State.RUNNING || _state == State.ACTIVE || _state == State.RUNNING || _state == State.ACTIVE ||
_state == State.GRACEFUL_SHUTDOWN; _state == State.GRACEFUL_SHUTDOWN;
} }
@ -401,7 +417,7 @@ public class RouterService extends Service {
} }
if(_state == State.STARTING || _state == State.RUNNING || if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P"); _statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping);
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED)); Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
stopperThread.start(); stopperThread.start();
} }
@ -423,7 +439,7 @@ public class RouterService extends Service {
} }
if(_state == State.STARTING || _state == State.RUNNING || if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P"); _statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping);
Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED)); Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED));
stopperThread.start(); stopperThread.start();
} else if(_state == State.WAITING) { } else if(_state == State.WAITING) {
@ -445,7 +461,7 @@ public class RouterService extends Service {
} }
if(_state == State.STARTING || _state == State.RUNNING || if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Network disconnected, stopping I2P"); _statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping_after_net);
// don't change state, let the shutdown hook do it // don't change state, let the shutdown hook do it
Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING)); Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING));
stopperThread.start(); stopperThread.start();
@ -465,7 +481,7 @@ public class RouterService extends Service {
if(!canManualStart()) { if(!canManualStart()) {
return; return;
} }
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up"); _statusBar.replace(StatusBar.ICON_STARTING, R.string.notification_status_starting);
setState(State.STARTING); setState(State.STARTING);
_starterThread = new Thread(new Starter()); _starterThread = new Thread(new Starter());
_starterThread.start(); _starterThread.start();
@ -513,9 +529,12 @@ public class RouterService extends Service {
_oldTitle = _currTitle; _oldTitle = _currTitle;
long ms = ctx.router().getShutdownTimeRemaining(); long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) { if (ms > 1000) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P in " + DataHelper.formatDuration(ms)); _statusBar.replace(
StatusBar.ICON_STOPPING,
getString(R.string.notification_status_graceful, DataHelper.formatDuration(ms))
);
} else { } else {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P"); _statusBar.replace(StatusBar.ICON_STOPPING, R.string.notification_status_stopping);
} }
setState(State.GRACEFUL_SHUTDOWN); setState(State.GRACEFUL_SHUTDOWN);
} }
@ -542,10 +561,10 @@ public class RouterService extends Service {
_currTitle = _oldTitle; _currTitle = _oldTitle;
if (_hadTunnels) { if (_hadTunnels) {
setState(State.ACTIVE); setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, "Shutdown cancelled"); _statusBar.replace(StatusBar.ICON_ACTIVE, R.string.notification_status_shutdown_cancelled);
} else { } else {
setState(State.RUNNING); setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, "Shutdown cancelled"); _statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_shutdown_cancelled);
} }
} }
} }
@ -608,6 +627,7 @@ public class RouterService extends Service {
_statusBar.remove(); _statusBar.remove();
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateRequested); LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateRequested);
LocalBroadcastManager.getInstance(this).unregisterReceiver(onLocaleChanged);
I2PReceiver rcvr = _receiver; I2PReceiver rcvr = _receiver;
if(rcvr != null) { if(rcvr != null) {
@ -628,7 +648,7 @@ public class RouterService extends Service {
if(_state == State.STARTING || _state == State.RUNNING || if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) { _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
// should this be in a thread? // 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)); Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
stopperThread.start(); stopperThread.start();
} }
@ -683,7 +703,7 @@ public class RouterService extends Service {
public void run() { public void run() {
Util.d(this + " shutdown hook" Util.d(this + " shutdown hook"
+ " Current state is: " + _state); + " 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; I2PReceiver rcvr = _receiver;
if(rcvr != null) { if(rcvr != null) {
synchronized(rcvr) { synchronized(rcvr) {

View File

@ -6,12 +6,13 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import net.i2p.android.router.MainActivity;
import net.i2p.android.I2PActivity;
import net.i2p.android.router.R; import net.i2p.android.router.R;
class StatusBar { class StatusBar {
private final Context ctx; private Context mCtx;
private final NotificationManager mNotificationManager; private final NotificationManager mNotificationManager;
private final NotificationCompat.Builder mNotifyBuilder; private final NotificationCompat.Builder mNotifyBuilder;
private Notification mNotif; private Notification mNotif;
@ -25,8 +26,8 @@ class StatusBar {
public static final int ICON_SHUTTING_DOWN = R.drawable.ic_stat_router_shutting_down; public static final int ICON_SHUTTING_DOWN = R.drawable.ic_stat_router_shutting_down;
public static final int ICON_WAITING_NETWORK = R.drawable.ic_stat_router_waiting_network; public static final int ICON_WAITING_NETWORK = R.drawable.ic_stat_router_waiting_network;
StatusBar(Context cx) { StatusBar(Context ctx) {
ctx = cx; mCtx = ctx;
mNotificationManager = (NotificationManager) ctx.getSystemService( mNotificationManager = (NotificationManager) ctx.getSystemService(
Context.NOTIFICATION_SERVICE); Context.NOTIFICATION_SERVICE);
Thread.currentThread().setUncaughtExceptionHandler( Thread.currentThread().setUncaughtExceptionHandler(
@ -34,7 +35,7 @@ class StatusBar {
int icon = ICON_STARTING; int icon = ICON_STARTING;
// won't be shown if replace() is called // won't be shown if replace() is called
String text = "Starting I2P"; String text = ctx.getString(R.string.notification_status_starting);
mNotifyBuilder = new NotificationCompat.Builder(ctx) mNotifyBuilder = new NotificationCompat.Builder(ctx)
.setContentText(text) .setContentText(text)
@ -42,22 +43,25 @@ class StatusBar {
.setOngoing(true) .setOngoing(true)
.setOnlyAlertOnce(true); .setOnlyAlertOnce(true);
Intent intent = new Intent(ctx, MainActivity.class); Intent intent = new Intent(ctx, I2PActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(pi); mNotifyBuilder.setContentIntent(pi);
} }
public void replace(int icon, String text) { public void replace(int icon, int textResource) {
mNotifyBuilder.setSmallIcon(icon) replace(icon, mCtx.getString(textResource));
.setStyle(null)
.setTicker(text);
update(text);
} }
public void update(String text) { public void replace(int icon, String title) {
String title = "I2P Status"; mNotifyBuilder.setSmallIcon(icon)
update(title, text); .setStyle(null)
.setTicker(title);
update(title);
}
public void update(String title) {
update(title, null);
} }
public void update(String title, String text, String bigText) { public void update(String title, String text, String bigText) {

View File

@ -1,8 +1,5 @@
package net.i2p.android.router.service; package net.i2p.android.router.service;
import java.util.Observable;
import java.util.Observer;
import com.androidplot.xy.SimpleXYSeries; import com.androidplot.xy.SimpleXYSeries;
import com.androidplot.xy.XYSeries; import com.androidplot.xy.XYSeries;
@ -11,6 +8,9 @@ import net.i2p.stat.Rate;
import net.i2p.stat.RateStat; import net.i2p.stat.RateStat;
import net.i2p.stat.RateSummaryListener; import net.i2p.stat.RateSummaryListener;
import java.util.Observable;
import java.util.Observer;
public class SummaryListener implements RateSummaryListener { public class SummaryListener implements RateSummaryListener {
public static final int HISTORY_SIZE = 30; public static final int HISTORY_SIZE = 30;
@ -45,8 +45,7 @@ public class SummaryListener implements RateSummaryListener {
public void add(double totalValue, long eventCount, double totalEventTime, public void add(double totalValue, long eventCount, double totalEventTime,
long period) { long period) {
long now = now(); long when = now();
long when = now / 1000;
double val = eventCount > 0 ? (totalValue / eventCount) : 0d; double val = eventCount > 0 ? (totalValue / eventCount) : 0d;
if (_series.size() > HISTORY_SIZE) if (_series.size() > HISTORY_SIZE)
@ -70,7 +69,6 @@ public class SummaryListener implements RateSummaryListener {
long period = _rate.getPeriod(); long period = _rate.getPeriod();
_name = rs.getName() + "." + period; _name = rs.getName() + "." + period;
_series = new SimpleXYSeries(_name); _series = new SimpleXYSeries(_name);
_series.useImplicitXVals();
_rate.setSummaryListener(this); _rate.setSummaryListener(this);
} }

View File

@ -1,15 +1,22 @@
package net.i2p.android.router.stats; package net.i2p.android.router.stats;
import net.i2p.android.router.I2PActivityBase; 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.R;
import net.i2p.android.router.service.RouterService; import net.i2p.android.router.service.RouterService;
import android.os.Bundle;
public class PeersActivity extends I2PActivityBase { public class PeersActivity extends I2PActivityBase {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false); setContentView(R.layout.activity_onepane);
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Start with the base view // Start with the base view
if (savedInstanceState == null) { if (savedInstanceState == null) {
PeersFragment f = new PeersFragment(); PeersFragment f = new PeersFragment();

View File

@ -1,23 +1,21 @@
package net.i2p.android.router.stats; package net.i2p.android.router.stats;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog;
import android.support.v4.app.DialogFragment; import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Spinner; 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.R;
import net.i2p.android.router.SettingsActivity; import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.service.StatSummarizer; import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener; import net.i2p.android.router.service.SummaryListener;
import net.i2p.android.router.util.Util;
import net.i2p.stat.Rate; import net.i2p.stat.Rate;
import java.util.Comparator; import java.util.Comparator;
@ -36,9 +34,35 @@ public class RateGraphActivity extends I2PActivityBase {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false); setContentView(R.layout.activity_onepane);
if (StatSummarizer.instance() != null) { Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (Util.getRouterContext() == null) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.router_not_running)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
})
.setCancelable(false);
builder.show();
} else if (StatSummarizer.instance() == null) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.graphs_not_ready)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
})
.setCancelable(false);
builder.show();
} else {
// Get the rates currently being graphed // Get the rates currently being graphed
List<SummaryListener> listeners = StatSummarizer.instance().getListeners(); List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
TreeSet<SummaryListener> ordered = new TreeSet<>(new AlphaComparator()); TreeSet<SummaryListener> ordered = new TreeSet<>(new AlphaComparator());
@ -79,57 +103,27 @@ public class RateGraphActivity extends I2PActivityBase {
} else } else
selectRate(0); selectRate(0);
} else { } else {
DialogFragment df = new DialogFragment() { AlertDialog.Builder builder = new AlertDialog.Builder(this);
@NonNull builder.setMessage(R.string.no_graphs_configured)
@Override .setPositiveButton(R.string.configure_graphs, new DialogInterface.OnClickListener() {
public Dialog onCreateDialog(Bundle savedInstanceState) { public void onClick(DialogInterface dialog, int which) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); dialog.dismiss();
builder.setMessage(R.string.no_graphs_configured) mFinishOnResume = true;
.setPositiveButton(R.string.configure_graphs, new DialogInterface.OnClickListener() { Intent i = new Intent(RateGraphActivity.this, SettingsActivity.class);
public void onClick(DialogInterface dialog, int which) { i.putExtra(SettingsActivity.PREFERENCE_CATEGORY, SettingsActivity.PREFERENCE_CATEGORY_GRAPHS);
dialog.dismiss(); startActivity(i);
mFinishOnResume = true; }
Intent i = new Intent(RateGraphActivity.this, SettingsActivity.class); })
// Navigation to a sub-category doesn't seem to work yet .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { @Override
i.setAction(SettingsActivity.ACTION_PREFS_GRAPHS); public void onClick(DialogInterface dialog, int i) {
} else { dialog.cancel();
i.putExtra("settings", "graphs"); finish();
} }
startActivity(i); })
} .setCancelable(false);
}) builder.show();
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
finish();
}
})
.setCancelable(false);
return builder.create();
}
};
df.show(getSupportFragmentManager(), "nographs");
} }
} else {
DialogFragment df = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.graphs_not_ready)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
})
.setCancelable(false);
return builder.create();
}
};
df.show(getSupportFragmentManager(), "graphsnotready");
} }
} }

View File

@ -8,9 +8,9 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.androidplot.Plot; import com.androidplot.xy.BarFormatter;
import com.androidplot.xy.BarRenderer;
import com.androidplot.xy.BoundaryMode; import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.XYPlot; import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeries; import com.androidplot.xy.XYSeries;
import com.androidplot.xy.XYStepMode; import com.androidplot.xy.XYStepMode;
@ -21,25 +21,22 @@ import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener; import net.i2p.android.router.service.SummaryListener;
import net.i2p.android.router.util.Util; import net.i2p.android.router.util.Util;
import java.text.DateFormat;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.FieldPosition; import java.text.FieldPosition;
import java.text.Format; import java.text.Format;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
public class RateGraphFragment extends I2PFragmentBase { public class RateGraphFragment extends I2PFragmentBase {
// redraws a plot whenever an update is received: // redraws a plot whenever an update is received:
private class MyPlotUpdater implements Observer { private class MyPlotUpdater implements Observer {
Plot plot;
public MyPlotUpdater(Plot plot) {
this.plot = plot;
}
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
Util.d("Redrawing plot"); Util.d("Redrawing plot");
plot.redraw(); updatePlot();
} }
} }
@ -51,6 +48,7 @@ public class RateGraphFragment extends I2PFragmentBase {
private SummaryListener _listener; private SummaryListener _listener;
private XYPlot _ratePlot; private XYPlot _ratePlot;
private MyPlotUpdater _plotUpdater; private MyPlotUpdater _plotUpdater;
private int _k;
public static RateGraphFragment newInstance(String name, long period) { public static RateGraphFragment newInstance(String name, long period) {
RateGraphFragment f = new RateGraphFragment(); RateGraphFragment f = new RateGraphFragment();
@ -70,7 +68,7 @@ public class RateGraphFragment extends I2PFragmentBase {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_graph, container, false); View v = inflater.inflate(R.layout.fragment_graph, container, false);
_ratePlot = (XYPlot) v.findViewById(R.id.rate_stat_plot); _ratePlot = (XYPlot) v.findViewById(R.id.rate_stat_plot);
@ -99,6 +97,9 @@ public class RateGraphFragment extends I2PFragmentBase {
public void run() { public void run() {
String rateName = getArguments().getString(RATE_NAME); String rateName = getArguments().getString(RATE_NAME);
long period = getArguments().getLong(RATE_PERIOD); long period = getArguments().getLong(RATE_PERIOD);
_k = 1000;
if (rateName.startsWith("bw.") || rateName.contains("Size") || rateName.contains("Bps") || rateName.contains("memory"))
_k = 1024;
Util.d("Setting up " + rateName + "." + period); Util.d("Setting up " + rateName + "." + period);
if (StatSummarizer.instance() == null) { if (StatSummarizer.instance() == null) {
@ -115,38 +116,68 @@ public class RateGraphFragment extends I2PFragmentBase {
XYSeries rateSeries = _listener.getSeries(); XYSeries rateSeries = _listener.getSeries();
_plotUpdater = new MyPlotUpdater(_ratePlot); _plotUpdater = new MyPlotUpdater();
_ratePlot.addSeries(rateSeries, new LineAndPointFormatter(Color.rgb(0, 0, 0), null, Color.rgb(0, 80, 0), null)); _ratePlot.addSeries(rateSeries, new BarFormatter(Color.argb(200, 0, 80, 0), Color.argb(200, 0, 80, 0)));
_ratePlot.calculateMinMaxVals();
long maxX = _ratePlot.getCalculatedMaxX().longValue();
Util.d("Adding plot updater to listener"); Util.d("Adding plot updater to listener");
_listener.addObserver(_plotUpdater); _listener.addObserver(_plotUpdater);
_ratePlot.setDomainStepMode(XYStepMode.SUBDIVIDE); // Only one line, so hide the legend
_ratePlot.setDomainStepValue(SummaryListener.HISTORY_SIZE); _ratePlot.getLegendWidget().setVisible(false);
// thin out domain/range tick labels so they dont overlap each other: BarRenderer renderer = (BarRenderer) _ratePlot.getRenderer(BarRenderer.class);
_ratePlot.setTicksPerDomainLabel(5); renderer.setBarWidthStyle(BarRenderer.BarWidthStyle.VARIABLE_WIDTH);
_ratePlot.setTicksPerRangeLabel(3); renderer.setBarGap(0);
_ratePlot.setDomainUpperBoundary(maxX, BoundaryMode.GROW);
_ratePlot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 15 * 60 * 1000);
_ratePlot.setTicksPerDomainLabel(4);
_ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED); _ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
_ratePlot.setTicksPerRangeLabel(5);
_ratePlot.setDomainValueFormat(new Format() {
private DateFormat dateFormat = SimpleDateFormat.getTimeInstance(DateFormat.SHORT);
@Override
public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
@NonNull FieldPosition pos) {
long when = ((Number) obj).longValue();
Date date = new Date(when);
return dateFormat.format(date, toAppendTo, pos);
}
@Override
public Object parseObject(String s, @NonNull ParsePosition parsePosition) {
return null;
}
});
final int finalK = _k;
_ratePlot.setRangeValueFormat(new Format() { _ratePlot.setRangeValueFormat(new Format() {
@Override @Override
public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo, public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
@NonNull FieldPosition pos) { @NonNull FieldPosition pos) {
double val = ((Number) obj).doubleValue(); double val = ((Number) obj).doubleValue();
if (val >= 10 * 1000 * 1000) double maxY = _ratePlot.getCalculatedMaxY().doubleValue();
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
else if (val >= 8 * 100 * 1000) if (val == 0 || maxY < finalK) {
return new DecimalFormat("0.0 M").format(val / (1000 * 1000), toAppendTo, pos);
else if (val >= 10 * 1000)
return new DecimalFormat("0 k").format(val / (1000), toAppendTo, pos);
else if (val >= 8 * 100)
return new DecimalFormat("0.0 k").format(val / (1000), toAppendTo, pos);
else
return new DecimalFormat("0").format(val, toAppendTo, pos); return new DecimalFormat("0").format(val, toAppendTo, pos);
} else if (maxY < finalK * finalK) {
if (val < 10 * finalK)
return new DecimalFormat("0.0 k").format(val / (1000), toAppendTo, pos);
else
return new DecimalFormat("0 k").format(val / (1000), toAppendTo, pos);
} else {
if (val < 10 * finalK * finalK)
return new DecimalFormat("0.0 M").format(val / (finalK * finalK), toAppendTo, pos);
else
return new DecimalFormat("0 M").format(val / (finalK * finalK), toAppendTo, pos);
}
} }
@Override @Override
@ -157,7 +188,45 @@ public class RateGraphFragment extends I2PFragmentBase {
}); });
Util.d("Redrawing plot"); Util.d("Redrawing plot");
_ratePlot.redraw(); updatePlot();
} }
} }
private void updatePlot() {
_ratePlot.calculateMinMaxVals();
double maxY = _ratePlot.getCalculatedMaxY().doubleValue();
_ratePlot.setRangeStep(XYStepMode.INCREMENT_BY_VAL, getRangeStep(maxY, _k));
_ratePlot.redraw();
}
private double getRangeStep(double maxY, int k) {
if (maxY >= k * k)
return getRangeStepForScale(maxY, k * k);
else if (maxY >= k)
return getRangeStepForScale(maxY, k);
else
return getRangeStepForScale(maxY, 1);
}
private double getRangeStepForScale(double maxY, int scale) {
if (maxY >= 400 * scale)
return 40 * scale;
else if (maxY >= 200 * scale)
return 20 * scale;
else if (maxY >= 100 * scale)
return 10 * scale;
else if (maxY >= 40 * scale)
return 4 * scale;
else if (maxY >= 20 * scale)
return 2 * scale;
else if (maxY >= 10 * scale)
return scale;
else if (maxY >= 4 * scale)
return 0.4 * scale;
else if (maxY >= 2 * scale)
return 0.2 * scale;
else
return 0.1 * scale;
}
} }

View File

@ -15,14 +15,27 @@ public class IntListPreference extends ListPreference {
@Override @Override
protected boolean persistString(String value) { 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)); return value != null && persistInt(Integer.valueOf(value));
} }
@Override @Override
protected String getPersistedString(String defaultReturnValue) { protected String getPersistedString(String defaultReturnValue) {
if(getSharedPreferences().contains(getKey())) { if(getSharedPreferences().contains(getKey())) {
int intValue = getPersistedInt(0); try {
return String.valueOf(intValue); int intValue = getPersistedInt(0);
return String.valueOf(intValue);
} catch (ClassCastException e) {
return super.getPersistedString("0");
}
} else { } else {
return defaultReturnValue; return defaultReturnValue;
} }

View File

@ -6,13 +6,21 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.TextUtils;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.android.preferences.GraphsPreferenceFragment;
import net.i2p.android.router.I2PConstants; import net.i2p.android.router.I2PConstants;
import net.i2p.android.router.R;
import net.i2p.android.router.service.State;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.Router; import net.i2p.router.Router;
import net.i2p.router.RouterContext; import net.i2p.router.RouterContext;
import net.i2p.router.transport.TransportManager; import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.TransportUtil;
import net.i2p.router.transport.udp.UDPTransport; import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.OrderedProperties; import net.i2p.util.OrderedProperties;
@ -20,6 +28,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -40,7 +49,8 @@ public abstract class Util implements I2PConstants {
//System.err.println("APK Path" + ": " + _apkPath); //System.err.println("APK Path" + ": " + _apkPath);
if (pi.versionName != null) if (pi.versionName != null)
return pi.versionName; return pi.versionName;
} catch (Exception e) {} } catch (Exception e) {
}
return "??"; return "??";
} }
@ -51,7 +61,7 @@ public abstract class Util implements I2PConstants {
*/ */
public static RouterContext getRouterContext() { public static RouterContext getRouterContext() {
List<RouterContext> contexts = RouterContext.listContexts(); List<RouterContext> contexts = RouterContext.listContexts();
if ( !((contexts == null) || (contexts.isEmpty())) ) { if (!((contexts == null) || (contexts.isEmpty()))) {
return contexts.get(0); return contexts.get(0);
} }
return null; return null;
@ -64,8 +74,8 @@ public abstract class Util implements I2PConstants {
} }
/** /**
* Log to the context logger if available (which goes to the console buffer * Log to the context logger if available (which goes to the console buffer
* and to logcat), else just to logcat. * and to logcat), else just to logcat.
*/ */
public static void e(String m, Throwable t) { public static void e(String m, Throwable t) {
I2PAppContext ctx = I2PAppContext.getCurrentContext(); I2PAppContext ctx = I2PAppContext.getCurrentContext();
@ -110,6 +120,7 @@ public abstract class Util implements I2PConstants {
android.util.Log.i(ANDROID_TAG, m); android.util.Log.i(ANDROID_TAG, m);
} }
} }
public static void d(String m) { public static void d(String m) {
d(m, null); d(m, null);
} }
@ -126,7 +137,9 @@ public abstract class Util implements I2PConstants {
} }
} }
/** copied from various private components */ /**
* copied from various private components
*/
final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port"; final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport"; final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
@ -165,13 +178,30 @@ public abstract class Util implements I2PConstants {
String string = all.get(x).toString(); String string = all.get(x).toString();
String inverted = Boolean.toString(!Boolean.parseBoolean(string)); String inverted = Boolean.toString(!Boolean.parseBoolean(string));
routerProps.setProperty(x, inverted); routerProps.setProperty(x, inverted);
} else if (x.equals(context.getString(R.string.PREF_LANGUAGE))) {
String language[] = TextUtils.split(all.get(x).toString(), "_");
if (language[0].equals(context.getString(R.string.DEFAULT_LANGUAGE))) {
toRemove.setProperty("routerconsole.lang", "");
toRemove.setProperty("routerconsole.country", "");
} else {
routerProps.setProperty("routerconsole.lang", language[0].toLowerCase());
if (language.length == 2)
routerProps.setProperty("routerconsole.country", language[1].toUpperCase());
else
toRemove.setProperty("routerconsole.country", "");
}
} else if (!x.startsWith(ANDROID_PREF_PREFIX)) { // Skip over UI-related I2P Android settings } else if (!x.startsWith(ANDROID_PREF_PREFIX)) { // Skip over UI-related I2P Android settings
String string = all.get(x).toString(); String string = all.get(x).toString();
routerProps.setProperty(x, string); routerProps.setProperty(x, string);
} }
} }
if (statSummaries.isEmpty()) { if (statSummaries.isEmpty()) {
routerProps.setProperty("stat.summaries", ""); // If the graph preferences have not yet been seen, they should be the default
if (preferences.getBoolean(GraphsPreferenceFragment.GRAPH_PREFERENCES_SEEN, false))
routerProps.setProperty("stat.summaries", "");
else
toRemove.setProperty("stat.summaries", "");
} else { } else {
Iterator<String> iter = statSummaries.iterator(); Iterator<String> iter = statSummaries.iterator();
StringBuilder buf = new StringBuilder(iter.next()); StringBuilder buf = new StringBuilder(iter.next());
@ -203,6 +233,7 @@ public abstract class Util implements I2PConstants {
// propName -> defaultValue // propName -> defaultValue
private static HashMap<String, Boolean> booleanOptionsRequiringRestart = new HashMap<>(); private static HashMap<String, Boolean> booleanOptionsRequiringRestart = new HashMap<>();
private static HashMap<String, String> stringOptionsRequiringRestart = new HashMap<>(); private static HashMap<String, String> stringOptionsRequiringRestart = new HashMap<>();
static { static {
HashMap<String, Boolean> boolToAdd = new HashMap<>(); HashMap<String, Boolean> boolToAdd = new HashMap<>();
HashMap<String, String> strToAdd = new HashMap<>(); HashMap<String, String> strToAdd = new HashMap<>();
@ -219,6 +250,7 @@ public abstract class Util implements I2PConstants {
booleanOptionsRequiringRestart.putAll(boolToAdd); booleanOptionsRequiringRestart.putAll(boolToAdd);
stringOptionsRequiringRestart.putAll(strToAdd); stringOptionsRequiringRestart.putAll(strToAdd);
} }
/** /**
* This function performs two tasks: * This function performs two tasks:
* <ul><li> * <ul><li>
@ -230,7 +262,7 @@ public abstract class Util implements I2PConstants {
* changed that will require a router restart. * changed that will require a router restart.
* </li></ul> * </li></ul>
* *
* @param props a Properties object containing the router.config * @param props a Properties object containing the router.config
* @param toRemove a Collection of properties that will be removed * @param toRemove a Collection of properties that will be removed
* @return true if the router needs to be restarted. * @return true if the router needs to be restarted.
*/ */
@ -271,26 +303,26 @@ public abstract class Util implements I2PConstants {
} }
/** /**
* Write properties to a file. If the file does not exist, it is created. * Write properties to a file. If the file does not exist, it is created.
* If the properties already exist in the file, they are updated. * If the properties already exist in the file, they are updated.
* *
* @param dir the file directory * @param dir the file directory
* @param file relative to dir * @param file relative to dir
* @param props properties to set * @param props properties to set
*/ */
public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) { public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) {
mergeResourceToFile(ctx, dir, file, 0, props, null); mergeResourceToFile(ctx, dir, file, 0, props, null);
} }
/** /**
* Load defaults from resource, then add props from settings, and write back. * Load defaults from resource, then add props from settings, and write back.
* If resID is 0, defaults are not written over the existing file content. * If resID is 0, defaults are not written over the existing file content.
* *
* @param dir the file directory * @param dir the file directory
* @param file relative to dir * @param file relative to dir
* @param resID the ID of the default resource, or 0 * @param resID the ID of the default resource, or 0
* @param userProps local properties or null * @param userProps local properties or null
* @param toRemove properties to remove, or null * @param toRemove properties to remove, or null
*/ */
public static void mergeResourceToFile(Context ctx, String dir, String file, int resID, public static void mergeResourceToFile(Context ctx, String dir, String file, int resID,
Properties userProps, Collection<String> toRemove) { Properties userProps, Collection<String> toRemove) {
@ -317,7 +349,7 @@ public abstract class Util implements I2PConstants {
if (resID > 0) if (resID > 0)
in = ctx.getResources().openRawResource(resID); in = ctx.getResources().openRawResource(resID);
if (in != null) if (in != null)
DataHelper.loadProps(props, in); DataHelper.loadProps(props, in);
// override with user settings // override with user settings
if (userProps != null) if (userProps != null)
@ -330,12 +362,232 @@ public abstract class Util implements I2PConstants {
File path = new File(dir, file); File path = new File(dir, file);
DataHelper.storeProps(props, path); DataHelper.storeProps(props, path);
Util.d("Saved " + props.size() +" properties in " + file); Util.d("Saved " + props.size() + " properties in " + file);
} catch (IOException ioe) { } catch (IOException ioe) {
} catch (Resources.NotFoundException nfe) { } catch (Resources.NotFoundException nfe) {
} finally { } finally {
if (in != null) try { in.close(); } catch (IOException ioe) {} if (in != null) try {
if (fin != null) try { fin.close(); } catch (IOException ioe) {} in.close();
} catch (IOException ioe) {
}
if (fin != null) try {
fin.close();
} catch (IOException ioe) {
}
}
}
public static boolean isStopping(State state) {
return state == State.STOPPING ||
state == State.MANUAL_STOPPING ||
state == State.MANUAL_QUITTING;
}
public static boolean isStopped(State state) {
return state == State.STOPPED ||
state == State.MANUAL_STOPPED ||
state == State.MANUAL_QUITTED ||
state == State.WAITING;
}
public static class NetStatus {
public enum Level {
ERROR,
WARN,
INFO,
}
public final Level level;
public final String status;
public NetStatus(Level level, String status) {
this.level = level;
this.status = status;
}
}
public static NetStatus getNetStatus(Context ctx, RouterContext rCtx) {
if (rCtx.commSystem().isDummy())
return new NetStatus(NetStatus.Level.INFO, ctx.getString(R.string.vm_comm_system));
if (rCtx.router().getUptime() > 60 * 1000 && (!rCtx.router().gracefulShutdownInProgress()) &&
!rCtx.clientManager().isAlive()) // not a router problem but the user should know
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_i2cp));
// Warn based on actual skew from peers, not update status, so if we successfully offset
// the clock, we don't complain.
//if (!rCtx.clock().getUpdatedSuccessfully())
long skew = rCtx.commSystem().getFramedAveragePeerClockSkew(33);
// Display the actual skew, not the offset
if (Math.abs(skew) > 30 * 1000)
return new NetStatus(NetStatus.Level.ERROR,
ctx.getString(R.string.net_status_error_skew,
DataHelper.formatDuration2(Math.abs(skew))
.replace("&minus;", "-")
.replace("&nbsp;", " ")));
if (rCtx.router().isHidden())
return new NetStatus(NetStatus.Level.INFO, ctx.getString(R.string.hidden));
RouterInfo routerInfo = rCtx.router().getRouterInfo();
if (routerInfo == null)
return new NetStatus(NetStatus.Level.INFO, ctx.getString(R.string.testing));
CommSystemFacade.Status status = rCtx.commSystem().getStatus();
switch (status) {
case OK:
case IPV4_OK_IPV6_UNKNOWN:
case IPV4_OK_IPV6_FIREWALLED:
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_DISABLED_IPV6_OK:
case IPV4_SNAT_IPV6_OK:
RouterAddress ra = routerInfo.getTargetAddress("NTCP");
if (ra == null)
return new NetStatus(NetStatus.Level.INFO, toStatusString(ctx, status));
byte[] ip = ra.getIP();
if (ip == null)
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_unresolved_tcp));
// TODO set IPv6 arg based on configuration?
if (TransportUtil.isPubliclyRoutable(ip, true))
return new NetStatus(NetStatus.Level.INFO, toStatusString(ctx, status));
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_private_tcp));
case IPV4_SNAT_IPV6_UNKNOWN:
case DIFFERENT:
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.symmetric_nat));
case REJECT_UNSOLICITED:
case IPV4_DISABLED_IPV6_FIREWALLED:
if (routerInfo.getTargetAddress("NTCP") != null)
return new NetStatus(NetStatus.Level.WARN, ctx.getString(R.string.net_status_warn_firewalled_inbound_tcp));
// fall through...
case IPV4_FIREWALLED_IPV6_OK:
case IPV4_FIREWALLED_IPV6_UNKNOWN:
if (rCtx.netDb().floodfillEnabled())
return new NetStatus(NetStatus.Level.WARN, ctx.getString(R.string.net_status_warn_firewalled_floodfill));
//if (rCtx.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
// return _("WARN-Firewalled and Fast");
return new NetStatus(NetStatus.Level.INFO, toStatusString(ctx, status));
case DISCONNECTED:
return new NetStatus(NetStatus.Level.INFO, ctx.getString(R.string.net_status_info_disconnected));
case HOSED:
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_udp_port));
case UNKNOWN:
case IPV4_UNKNOWN_IPV6_FIREWALLED:
case IPV4_DISABLED_IPV6_UNKNOWN:
default:
ra = routerInfo.getTargetAddress("SSU");
if (ra == null && rCtx.router().getUptime() > 5 * 60 * 1000) {
if (rCtx.commSystem().countActivePeers() <= 0)
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_no_active_peers));
else if (rCtx.getProperty(ctx.getString(R.string.PROP_I2NP_NTCP_HOSTNAME)) == null ||
rCtx.getProperty(ctx.getString(R.string.PROP_I2NP_NTCP_PORT)) == null)
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_udp_disabled_tcp_not_set));
else
return new NetStatus(NetStatus.Level.WARN, ctx.getString(R.string.net_status_warn_firewalled_udp_disabled));
}
return new NetStatus(NetStatus.Level.INFO, toStatusString(ctx, status));
}
}
private static String toStatusString(Context ctx, CommSystemFacade.Status status) {
String ipv4Status = "";
String ipv6Status = "";
switch (status) {
case OK:
return ctx.getString(android.R.string.ok);
case IPV4_OK_IPV6_UNKNOWN:
ipv4Status = ctx.getString(android.R.string.ok);
ipv6Status = ctx.getString(R.string.testing);
break;
case IPV4_OK_IPV6_FIREWALLED:
ipv4Status = ctx.getString(android.R.string.ok);
ipv6Status = ctx.getString(R.string.firewalled);
break;
case IPV4_UNKNOWN_IPV6_OK:
ipv4Status = ctx.getString(R.string.testing);
ipv6Status = ctx.getString(android.R.string.ok);
break;
case IPV4_FIREWALLED_IPV6_OK:
ipv4Status = ctx.getString(R.string.firewalled);
ipv6Status = ctx.getString(android.R.string.ok);
break;
case IPV4_DISABLED_IPV6_OK:
ipv4Status = ctx.getString(R.string.disabled);
ipv6Status = ctx.getString(android.R.string.ok);
break;
case IPV4_SNAT_IPV6_OK:
ipv4Status = ctx.getString(R.string.symmetric_nat);
ipv6Status = ctx.getString(android.R.string.ok);
break;
case DIFFERENT:
return ctx.getString(R.string.symmetric_nat);
case IPV4_SNAT_IPV6_UNKNOWN:
ipv4Status = ctx.getString(R.string.symmetric_nat);
ipv6Status = ctx.getString(R.string.testing);
break;
case IPV4_FIREWALLED_IPV6_UNKNOWN:
ipv4Status = ctx.getString(R.string.firewalled);
ipv6Status = ctx.getString(R.string.testing);
break;
case REJECT_UNSOLICITED:
return ctx.getString(R.string.firewalled);
case IPV4_UNKNOWN_IPV6_FIREWALLED:
ipv4Status = ctx.getString(R.string.testing);
ipv6Status = ctx.getString(R.string.firewalled);
break;
case IPV4_DISABLED_IPV6_UNKNOWN:
ipv4Status = ctx.getString(R.string.disabled);
ipv6Status = ctx.getString(R.string.testing);
break;
case IPV4_DISABLED_IPV6_FIREWALLED:
ipv4Status = ctx.getString(R.string.disabled);
ipv6Status = ctx.getString(R.string.firewalled);
break;
case UNKNOWN:
return ctx.getString(R.string.testing);
default:
return status.toStatusString();
}
return ctx.getString(R.string.net_status_ipv4_ipv6, ipv4Status, ipv6Status);
}
public static String formatSize(double size) {
int scale;
for (scale = 0; size >= 1024.0D; size /= 1024.0D) {
++scale;
}
// control total width
DecimalFormat fmt;
if (size >= 1000) {
fmt = new DecimalFormat("#0");
} else if (size >= 100) {
fmt = new DecimalFormat("#0.0");
} else {
fmt = new DecimalFormat("#0.00");
}
String str = fmt.format(size);
switch (scale) {
case 1:
return str + "K";
case 2:
return str + "M";
case 3:
return str + "G";
case 4:
return str + "T";
case 5:
return str + "P";
case 6:
return str + "E";
case 7:
return str + "Z";
case 8:
return str + "Y";
default:
return str + "";
} }
} }
} }

View File

@ -11,12 +11,7 @@ import android.webkit.HttpAuthHandler;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.Toast; import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.android.apps.EepGetFetcher; import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.provider.CacheProvider; import net.i2p.android.router.provider.CacheProvider;
import net.i2p.android.router.util.AppCache; import net.i2p.android.router.util.AppCache;
@ -25,6 +20,13 @@ import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.util.EepGet; import net.i2p.util.EepGet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class I2PWebViewClient extends WebViewClient { public class I2PWebViewClient extends WebViewClient {
private BGLoad _lastTask; private BGLoad _lastTask;
@ -262,14 +264,14 @@ public class I2PWebViewClient extends WebViewClient {
} }
protected Integer doInBackground(final String... urls) { protected Integer doInBackground(final String... urls) {
publishProgress(Integer.valueOf(-1)); publishProgress(-1);
_view.post(new Runnable() { _view.post(new Runnable() {
@Override @Override
public void run() { public void run() {
_view.loadUrl(urls[0]); _view.loadUrl(urls[0]);
} }
}); });
return Integer.valueOf(0); return 0;
} }
@Override @Override
@ -316,10 +318,10 @@ public class I2PWebViewClient extends WebViewClient {
} }
}); });
// 1 means show the cache toast message // 1 means show the cache toast message
return Integer.valueOf(1); return 1;
} }
publishProgress(Integer.valueOf(-1)); publishProgress(-1);
//EepGetFetcher fetcher = new EepGetFetcher(url); //EepGetFetcher fetcher = new EepGetFetcher(url);
OutputStream out = null; OutputStream out = null;
try { try {
@ -330,7 +332,7 @@ public class I2PWebViewClient extends WebViewClient {
boolean success = fetcher.fetch(); boolean success = fetcher.fetch();
if (isCancelled()) { if (isCancelled()) {
Util.d("Fetch cancelled for " + url); Util.d("Fetch cancelled for " + url);
return Integer.valueOf(0); return 0;
} }
try { out.close(); } catch (IOException ioe) {} try { out.close(); } catch (IOException ioe) {}
if (success) { if (success) {
@ -342,7 +344,7 @@ public class I2PWebViewClient extends WebViewClient {
} else { } else {
AppCache.getInstance(_view.getContext()).removeCacheFile(uri); AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
Util.d("cache create error"); Util.d("cache create error");
return Integer.valueOf(0); return 0;
} }
Util.d("loading data, base URL: " + uri + " content URL: " + content); Util.d("loading data, base URL: " + uri + " content URL: " + content);
_view.post(new Runnable() { _view.post(new Runnable() {
@ -394,14 +396,14 @@ public class I2PWebViewClient extends WebViewClient {
} finally { } finally {
if (out != null) try { out.close(); } catch (IOException ioe) {} if (out != null) try { out.close(); } catch (IOException ioe) {}
} }
return Integer.valueOf(0); return 0;
} }
@Override @Override
protected void onProgressUpdate(Integer... progress) { protected void onProgressUpdate(Integer... progress) {
if (isCancelled()) if (isCancelled())
return; return;
int prog = progress[0].intValue(); int prog = progress[0];
if (prog < 0) { if (prog < 0) {
_dialog.setTitle("Contacting..."); _dialog.setTitle("Contacting...");
_dialog.setMessage(_host); _dialog.setMessage(_host);
@ -443,7 +445,7 @@ public class I2PWebViewClient extends WebViewClient {
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {} public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) { public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
publishProgress(Integer.valueOf(Math.max(0, (int) (alreadyTransferred + bytesTransferred)))); publishProgress(Math.max(0, (int) (alreadyTransferred + bytesTransferred)));
} }
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {} public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {}
@ -454,7 +456,7 @@ public class I2PWebViewClient extends WebViewClient {
if (key.equalsIgnoreCase("Content-Length")) { if (key.equalsIgnoreCase("Content-Length")) {
try { try {
_total = Integer.parseInt(val.trim()); _total = Integer.parseInt(val.trim());
publishProgress(Integer.valueOf(0)); publishProgress(0);
} catch (NumberFormatException nfe) {} } catch (NumberFormatException nfe) {}
} }
} }

View File

@ -1,13 +1,15 @@
package net.i2p.android.router.web; package net.i2p.android.router.web;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import android.os.Bundle; import android.os.Bundle;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.router.R;
public class WebActivity extends I2PActivityBase { public class WebActivity extends I2PActivityBase {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_fragment);
// Start with the base view // Start with the base view
if (savedInstanceState == null) { if (savedInstanceState == null) {
WebFragment f = new WebFragment(); WebFragment f = new WebFragment();
@ -18,13 +20,13 @@ public class WebActivity extends I2PActivityBase {
} else } else
f.setArguments(getIntent().getExtras()); f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit(); .add(R.id.fragment, f).commit();
} }
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
WebFragment f = (WebFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment); WebFragment f = (WebFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
if (!f.onBackPressed()) if (!f.onBackPressed())
super.onBackPressed(); super.onBackPressed();
} }

View File

@ -0,0 +1,76 @@
package net.i2p.android.util;
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.TextView;
import com.eowise.recyclerview.stickyheaders.StickyHeadersAdapter;
import net.i2p.android.router.R;
public class AlphanumericHeaderAdapter implements StickyHeadersAdapter<AlphanumericHeaderAdapter.ViewHolder> {
public interface SortedAdapter {
@NonNull
String getSortString(int position);
}
private static final String NUMBERS = "0123456789";
private SortedAdapter mAdapter;
private boolean mCombineNumeric;
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView character;
public ViewHolder(View itemView) {
super(itemView);
character = (TextView) itemView.findViewById(R.id.character);
}
}
public AlphanumericHeaderAdapter(SortedAdapter adapter) {
this(adapter, true);
}
public AlphanumericHeaderAdapter(SortedAdapter adapter, boolean combineNumeric) {
this.mAdapter = adapter;
this.mCombineNumeric = combineNumeric;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.header_alphanumeric, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder headerViewHolder, int position) {
String sortString = mAdapter.getSortString(position).toUpperCase();
if (sortString.isEmpty())
headerViewHolder.itemView.setVisibility(View.GONE);
else {
CharSequence character = sortString.subSequence(0, 1);
if (mCombineNumeric && NUMBERS.contains(character))
character = "0-9";
headerViewHolder.character.setText(character);
headerViewHolder.itemView.setVisibility(View.VISIBLE);
}
}
@Override
public long getHeaderId(int position) {
String sortString = mAdapter.getSortString(position).toUpperCase();
if (sortString.isEmpty())
return Integer.MAX_VALUE;
CharSequence character = sortString.subSequence(0, 1);
if (mCombineNumeric && NUMBERS.contains(character))
return "0-9".hashCode();
return sortString.charAt(0);
}
}

View File

@ -0,0 +1,33 @@
package net.i2p.android.util;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
public class FragmentUtils {
public interface TwoPaneProvider {
boolean isTwoPane();
}
/**
* @param frag
* The Fragment whose parent is to be found
* @param callbackInterface
* The interface class that the parent should implement
* @return The parent of frag that implements the callbackInterface or null
* if no such parent can be found
*/
@SuppressWarnings("unchecked") // Casts are checked using runtime methods
public static <T> T getParent(Fragment frag, Class<T> callbackInterface) {
Fragment parentFragment = frag.getParentFragment();
if (parentFragment != null
&& callbackInterface.isInstance(parentFragment)) {
return (T) parentFragment;
} else {
FragmentActivity activity = frag.getActivity();
if (activity != null && callbackInterface.isInstance(activity)) {
return (T) activity;
}
}
return null;
}
}

View File

@ -0,0 +1,65 @@
package net.i2p.android.util;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import net.i2p.android.router.R;
import java.util.Locale;
public class LocaleManager {
private Locale currentLocale;
public void onCreate(Activity activity) {
currentLocale = getSelectedLocale(activity);
setContextLocale(activity, currentLocale);
}
public void onResume(Activity activity) {
// If the activity has the incorrect locale, restart it
if (!currentLocale.equals(getSelectedLocale(activity))) {
Intent intent = activity.getIntent();
activity.finish();
activity.overridePendingTransition(0, 0);
activity.startActivity(intent);
activity.overridePendingTransition(0, 0);
}
}
public void updateServiceLocale(Service service) {
currentLocale = getSelectedLocale(service);
setContextLocale(service, currentLocale);
}
private static Locale getSelectedLocale(Context context) {
String defaultLanguage = context.getString(R.string.DEFAULT_LANGUAGE);
String selectedLanguage = PreferenceManager.getDefaultSharedPreferences(context).getString(
context.getResources().getString(R.string.PREF_LANGUAGE),
defaultLanguage
);
String language[] = TextUtils.split(selectedLanguage, "_");
if (language[0].equals(defaultLanguage))
return Locale.getDefault();
else if (language.length == 2)
return new Locale(language[0], language[1]);
else
return new Locale(language[0]);
}
private static void setContextLocale(Context context, Locale selectedLocale) {
Configuration configuration = context.getResources().getConfiguration();
if (!configuration.locale.equals(selectedLocale)) {
configuration.locale = selectedLocale;
context.getResources().updateConfiguration(
configuration,
context.getResources().getDisplayMetrics()
);
}
}
}

View File

@ -0,0 +1,39 @@
package net.i2p.android.util;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;
import java.util.HashMap;
import java.util.Map;
public abstract class MemoryFragmentPagerAdapter extends FragmentPagerAdapter {
private FragmentManager mFragmentManager;
private Map<Integer, String> mFragmentTags;
public MemoryFragmentPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<>();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment) {
// record the fragment tag here.
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
}
return obj;
}
public Fragment getFragment(int position) {
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
}
}

View File

@ -0,0 +1,49 @@
package net.i2p.android.widget;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
public class CustomViewPager extends ViewPager {
private boolean mEnabled;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mEnabled = false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mEnabled && super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mEnabled && super.onInterceptTouchEvent(event);
}
@Override
public void setCurrentItem(int item) {
if (mEnabled || item == 0)
super.setCurrentItem(item);
else
Toast.makeText(getContext(), Util.getRouterContext() == null ?
R.string.router_not_running : R.string.router_shutting_down,
Toast.LENGTH_SHORT).show();
}
public void setPagingEnabled(boolean enabled) {
mEnabled = enabled;
updatePagingState();
}
public void updatePagingState() {
if (!mEnabled && getCurrentItem() != 0)
setCurrentItem(0);
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.i2p.android.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.i2p.android.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import net.i2p.android.router.R;
public class DrawShadowFrameLayout extends FrameLayout {
private Drawable mShadowDrawable;
private NinePatchDrawable mShadowNinePatchDrawable;
private int mShadowTopOffset;
private boolean mShadowVisible;
private int mWidth, mHeight;
private ObjectAnimator mAnimator;
private float mAlpha = 1f;
public DrawShadowFrameLayout(Context context) {
this(context, null, 0);
}
public DrawShadowFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawShadowFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.DrawShadowFrameLayout, 0, 0);
mShadowDrawable = a.getDrawable(R.styleable.DrawShadowFrameLayout_shadowDrawable);
if (mShadowDrawable != null) {
mShadowDrawable.setCallback(this);
if (mShadowDrawable instanceof NinePatchDrawable) {
mShadowNinePatchDrawable = (NinePatchDrawable) mShadowDrawable;
}
}
mShadowVisible = a.getBoolean(R.styleable.DrawShadowFrameLayout_shadowVisible, true);
setWillNotDraw(!mShadowVisible || mShadowDrawable == null);
a.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
updateShadowBounds();
}
private void updateShadowBounds() {
if (mShadowDrawable != null) {
mShadowDrawable.setBounds(0, mShadowTopOffset, mWidth, mHeight);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mShadowDrawable != null && mShadowVisible) {
if (mShadowNinePatchDrawable != null) {
mShadowNinePatchDrawable.getPaint().setAlpha((int) (255 * mAlpha));
}
mShadowDrawable.draw(canvas);
}
}
public void setShadowTopOffset(int shadowTopOffset) {
this.mShadowTopOffset = shadowTopOffset;
updateShadowBounds();
ViewCompat.postInvalidateOnAnimation(this);
}
/* Unnecessary (for now)
public void setShadowVisible(boolean shadowVisible, boolean animate) {
this.mShadowVisible = shadowVisible;
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
if (animate && mShadowDrawable != null) {
mAnimator = ObjectAnimator.ofFloat(this, SHADOW_ALPHA,
shadowVisible ? 0f : 1f,
shadowVisible ? 1f : 0f);
mAnimator.setDuration(1000);
mAnimator.start();
}
ViewCompat.postInvalidateOnAnimation(this);
setWillNotDraw(!mShadowVisible || mShadowDrawable == null);
}
private static Property<DrawShadowFrameLayout, Float> SHADOW_ALPHA
= new Property<DrawShadowFrameLayout, Float>(Float.class, "shadowAlpha") {
@Override
public Float get(DrawShadowFrameLayout dsfl) {
return dsfl.mAlpha;
}
@Override
public void set(DrawShadowFrameLayout dsfl, Float value) {
dsfl.mAlpha = value;
ViewCompat.postInvalidateOnAnimation(dsfl);
}
};
*/
}

View File

@ -0,0 +1,86 @@
package net.i2p.android.widget;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import com.pnikosis.materialishprogress.ProgressWheel;
public class LoadingRecyclerView extends RecyclerView {
private View mLoadingView;
private ProgressWheel mLoadingWheel;
private boolean mLoading;
final private AdapterDataObserver observer = new AdapterDataObserver() {
@Override
public void onChanged() {
setLoading(false);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
setLoading(false);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
setLoading(false);
}
};
public LoadingRecyclerView(Context context) {
super(context);
}
public LoadingRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LoadingRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void updateLoading() {
if (mLoadingView != null) {
mLoadingView.setVisibility(mLoading ? VISIBLE : GONE);
setVisibility(mLoading ? GONE : VISIBLE);
if (mLoadingWheel != null) {
if (mLoading && !mLoadingWheel.isSpinning())
mLoadingWheel.spin();
else if (!mLoading && mLoadingWheel.isSpinning())
mLoadingWheel.stopSpinning();
}
}
}
@Override
public void setAdapter(Adapter adapter) {
final Adapter oldAdapter = getAdapter();
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(observer);
}
super.setAdapter(adapter);
if (adapter != null) {
adapter.registerAdapterDataObserver(observer);
}
}
/**
* Set the views to use for showing state.
* <p/>
* This method also sets the state to "loading".
*
* @param loadingView The view to show in place of the RecyclerView while loading.
* @param progressWheel The indeterminate ProgressWheel to spin while loading, if any.
*/
public void setLoadingView(View loadingView, ProgressWheel progressWheel) {
mLoadingView = loadingView;
mLoadingWheel = progressWheel;
setLoading(true);
}
public void setLoading(boolean loading) {
mLoading = loading;
updateLoading();
}
}

View File

@ -0,0 +1,325 @@
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.i2p.android.widget;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* To be used with ViewPager to provide a tab indicator component which give constant feedback as to
* the user's scroll progress.
* <p>
* To use the component, simply add it to your view hierarchy. Then in your
* {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
* {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for.
* <p>
* The colors can be customized in two ways. The first and simplest is to provide an array of colors
* via {@link #setSelectedIndicatorColors(int...)}. The
* alternative is via the {@link TabColorizer} interface which provides you complete control over
* which color is used for any individual position.
* <p>
* The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
* providing the layout ID of your custom layout.
*/
public class SlidingTabLayout extends HorizontalScrollView {
/**
* Allows complete control over the colors drawn in the tab layout. Set with
* {@link #setCustomTabColorizer(TabColorizer)}.
*/
public interface TabColorizer {
/**
* @return return the color of the indicator used when {@code position} is selected.
*/
int getIndicatorColor(int position);
}
private static final int TITLE_OFFSET_DIPS = 24;
private static final int TAB_VIEW_PADDING_DIPS = 16;
private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
private int mTitleOffset;
private int mTabViewLayoutId;
private int mTabViewTextViewId;
private boolean mDistributeEvenly;
private ViewPager mViewPager;
private SparseArray<String> mContentDescriptions = new SparseArray<String>();
private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
private final SlidingTabStrip mTabStrip;
public SlidingTabLayout(Context context) {
this(context, null);
}
public SlidingTabLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Disable the Scroll Bar
setHorizontalScrollBarEnabled(false);
// Make sure that the Tab Strips fills this View
setFillViewport(true);
mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
mTabStrip = new SlidingTabStrip(context);
addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
/**
* Set the custom {@link TabColorizer} to be used.
*
* If you only require simple custmisation then you can use
* {@link #setSelectedIndicatorColors(int...)} to achieve
* similar effects.
*/
public void setCustomTabColorizer(TabColorizer tabColorizer) {
mTabStrip.setCustomTabColorizer(tabColorizer);
}
public void setDistributeEvenly(boolean distributeEvenly) {
mDistributeEvenly = distributeEvenly;
}
/**
* Sets the colors to be used for indicating the selected tab. These colors are treated as a
* circular array. Providing one color will mean that all tabs are indicated with the same color.
*/
public void setSelectedIndicatorColors(int... colors) {
mTabStrip.setSelectedIndicatorColors(colors);
}
/**
* Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
* required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
* that the layout can update it's scroll position correctly.
*
* @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
*/
public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
mViewPagerPageChangeListener = listener;
}
/**
* Set the custom layout to be inflated for the tab views.
*
* @param layoutResId Layout id to be inflated
* @param textViewId id of the {@link TextView} in the inflated view
*/
public void setCustomTabView(int layoutResId, int textViewId) {
mTabViewLayoutId = layoutResId;
mTabViewTextViewId = textViewId;
}
/**
* Sets the associated view pager. Note that the assumption here is that the pager content
* (number of tabs and tab titles) does not change after this call has been made.
*/
public void setViewPager(ViewPager viewPager) {
mTabStrip.removeAllViews();
mViewPager = viewPager;
if (viewPager != null) {
viewPager.setOnPageChangeListener(new InternalViewPagerListener());
populateTabStrip();
}
}
/**
* Create a default view to be used for tabs. This is called if a custom tab view is not set via
* {@link #setCustomTabView(int, int)}.
*/
protected TextView createDefaultTabView(Context context) {
TextView textView = new TextView(context);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
textView.setTypeface(Typeface.DEFAULT_BOLD);
textView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
outValue, true);
textView.setBackgroundResource(outValue.resourceId);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
textView.setAllCaps(true);
int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
textView.setPadding(padding, padding, padding, padding);
return textView;
}
private void populateTabStrip() {
final PagerAdapter adapter = mViewPager.getAdapter();
final View.OnClickListener tabClickListener = new TabClickListener();
for (int i = 0; i < adapter.getCount(); i++) {
View tabView = null;
TextView tabTitleView = null;
if (mTabViewLayoutId != 0) {
// If there is a custom tab view layout id set, try and inflate it
tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
false);
tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
}
if (tabView == null) {
tabView = createDefaultTabView(getContext());
}
if (tabTitleView == null && TextView.class.isInstance(tabView)) {
tabTitleView = (TextView) tabView;
}
if (mDistributeEvenly) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams();
lp.width = 0;
lp.weight = 1;
}
tabTitleView.setText(adapter.getPageTitle(i));
tabView.setOnClickListener(tabClickListener);
String desc = mContentDescriptions.get(i, null);
if (desc != null) {
tabView.setContentDescription(desc);
}
mTabStrip.addView(tabView);
if (i == mViewPager.getCurrentItem()) {
tabView.setSelected(true);
}
}
}
public void setContentDescription(int i, String desc) {
mContentDescriptions.put(i, desc);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mViewPager != null) {
scrollToTab(mViewPager.getCurrentItem(), 0);
}
}
private void scrollToTab(int tabIndex, int positionOffset) {
final int tabStripChildCount = mTabStrip.getChildCount();
if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
return;
}
View selectedChild = mTabStrip.getChildAt(tabIndex);
if (selectedChild != null) {
int targetScrollX = selectedChild.getLeft() + positionOffset;
if (tabIndex > 0 || positionOffset > 0) {
// If we're not at the first child and are mid-scroll, make sure we obey the offset
targetScrollX -= mTitleOffset;
}
scrollTo(targetScrollX, 0);
}
}
private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
private int mScrollState;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int tabStripChildCount = mTabStrip.getChildCount();
if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
return;
}
mTabStrip.onViewPagerPageChanged(position, positionOffset);
View selectedTitle = mTabStrip.getChildAt(position);
int extraOffset = (selectedTitle != null)
? (int) (positionOffset * selectedTitle.getWidth())
: 0;
scrollToTab(position, extraOffset);
if (mViewPagerPageChangeListener != null) {
mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
positionOffsetPixels);
}
}
@Override
public void onPageScrollStateChanged(int state) {
mScrollState = state;
if (mViewPagerPageChangeListener != null) {
mViewPagerPageChangeListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageSelected(int position) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
mTabStrip.onViewPagerPageChanged(position, 0f);
scrollToTab(position, 0);
}
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
mTabStrip.getChildAt(i).setSelected(position == i);
}
if (mViewPagerPageChangeListener != null) {
mViewPagerPageChangeListener.onPageSelected(position);
}
}
}
private class TabClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
if (v == mTabStrip.getChildAt(i)) {
mViewPager.setCurrentItem(i);
return;
}
}
}
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.i2p.android.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;
class SlidingTabStrip extends LinearLayout {
private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0;
private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3;
private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
private final int mBottomBorderThickness;
private final Paint mBottomBorderPaint;
private final int mSelectedIndicatorThickness;
private final Paint mSelectedIndicatorPaint;
private final int mDefaultBottomBorderColor;
private int mSelectedPosition;
private float mSelectionOffset;
private SlidingTabLayout.TabColorizer mCustomTabColorizer;
private final SimpleTabColorizer mDefaultTabColorizer;
SlidingTabStrip(Context context) {
this(context, null);
}
SlidingTabStrip(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
final float density = getResources().getDisplayMetrics().density;
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true);
final int themeForegroundColor = outValue.data;
mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
mDefaultTabColorizer = new SimpleTabColorizer();
mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
mBottomBorderPaint = new Paint();
mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
mSelectedIndicatorPaint = new Paint();
}
void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
mCustomTabColorizer = customTabColorizer;
invalidate();
}
void setSelectedIndicatorColors(int... colors) {
// Make sure that the custom colorizer is removed
mCustomTabColorizer = null;
mDefaultTabColorizer.setIndicatorColors(colors);
invalidate();
}
void onViewPagerPageChanged(int position, float positionOffset) {
mSelectedPosition = position;
mSelectionOffset = positionOffset;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int height = getHeight();
final int childCount = getChildCount();
final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
? mCustomTabColorizer
: mDefaultTabColorizer;
// Thick colored underline below the current selection
if (childCount > 0) {
View selectedTitle = getChildAt(mSelectedPosition);
int left = selectedTitle.getLeft();
int right = selectedTitle.getRight();
int color = tabColorizer.getIndicatorColor(mSelectedPosition);
if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
if (color != nextColor) {
color = blendColors(nextColor, color, mSelectionOffset);
}
// Draw the selection partway between the tabs
View nextTitle = getChildAt(mSelectedPosition + 1);
left = (int) (mSelectionOffset * nextTitle.getLeft() +
(1.0f - mSelectionOffset) * left);
right = (int) (mSelectionOffset * nextTitle.getRight() +
(1.0f - mSelectionOffset) * right);
}
mSelectedIndicatorPaint.setColor(color);
canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
height, mSelectedIndicatorPaint);
}
// Thin underline along the entire bottom edge
canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
}
/**
* Set the alpha value of the {@code color} to be the given {@code alpha} value.
*/
private static int setColorAlpha(int color, byte alpha) {
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
}
/**
* Blend {@code color1} and {@code color2} using the given ratio.
*
* @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
* 0.0 will return {@code color2}.
*/
private static int blendColors(int color1, int color2, float ratio) {
final float inverseRation = 1f - ratio;
float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
return Color.rgb((int) r, (int) g, (int) b);
}
private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
private int[] mIndicatorColors;
@Override
public final int getIndicatorColor(int position) {
return mIndicatorColors[position % mIndicatorColors.length];
}
void setIndicatorColors(int... colors) {
mIndicatorColors = colors;
}
}
}

View File

@ -34,7 +34,7 @@ public class Conditional implements ModelCallbacks {
} }
public interface Condition { public interface Condition {
public boolean isSatisfied(); boolean isSatisfied();
} }
public class EqualCondition<T> implements Condition { public class EqualCondition<T> implements Condition {

View File

@ -22,6 +22,6 @@ import java.util.ArrayList;
* Represents a node in the page tree. Can either be a single page, or a page container. * Represents a node in the page tree. Can either be a single page, or a page container.
*/ */
public interface PageTreeNode { public interface PageTreeNode {
public Page findByKey(String key); Page findByKey(String key);
public void flattenCurrentPageSequence(ArrayList<Page> dest); void flattenCurrentPageSequence(ArrayList<Page> dest);
} }

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