Compare commits

...

623 Commits

Author SHA1 Message Date
234bc6e5a0 0.9.19
i2p.i2p tag: i2p-0.9.19
2015-04-13 02:20:51 +00:00
b0131843ae Handle saved strings from older version 2015-04-13 01:52:24 +00:00
c32bda66b5 Client library 0.9.19
i2p.i2p tag: i2p-0.9.19
2015-04-13 01:18:21 +00:00
6d726df1dc Updated translations 2015-04-13 00:01:20 +00:00
5b5a99f512 Removed duplicate info from tunnel details page 2015-04-10 10:32:19 +00:00
a11dd1e4e6 Rebuilt libjbigi.so for armeabi with new build.sh, new build for armeabi-v7a
Both tested on my local device, router starts and builds tunnels.

armeabi:     native = 77% of pure java time
armeabi-v7a: native = 62% of pure java time
2015-04-10 04:03:36 +00:00
2190c59d73 Updated jbigi build.sh to support building all ABIs
Now uses make-standalone-toolchain.sh and flags obtained from
https://github.com/Rupan/gmp
2015-04-10 03:59:24 +00:00
c6e06e25a8 Updated translations 2015-04-09 23:50:36 +00:00
a559eb4fab Fixed location of uk translations 2015-04-09 23:49:02 +00:00
d1110aefc4 Fixes for jbigi building 2015-04-09 13:56:10 +00:00
9a5c63f620 Updated translations 2015-04-08 21:53:41 +00:00
d4e9195a6c Updated translations 2015-04-07 12:27:17 +00:00
e9d2b9f53c String fix 2015-04-07 12:26:01 +00:00
40f3d91ebb Tunnel logic bugfix 2015-04-05 13:12:14 +00:00
fae4b7e42d Bugfix in converting wizard data to TunnelConfig 2015-04-05 13:11:53 +00:00
eb700e34ba Migrated TunnelUtil.createConfigFromWizard() to use TunnelLogic 2015-04-05 12:32:56 +00:00
6828d985d2 Updated TODO 2015-04-05 05:00:06 +00:00
05a2132295 Updated translations 2015-04-05 04:58:43 +00:00
dbee390bba Updated changelog 2015-04-05 04:42:10 +00:00
0b5ae4edf8 Updated @since 2015-04-05 04:38:35 +00:00
bd741cd500 Use two buttons for graceful shutdown, restore notification title on cancel 2015-04-05 04:30:44 +00:00
4d7bc0f92b propagate from branch 'i2p.android.base' (head b76931f62977a8f15bd906d380795eddb0419d2a)
to branch 'i2p.android.base.zzz.graceful' (head 73baffc0c318d0c1b8c7ba05327ceefb2c84b341)
2015-04-05 02:41:52 +00:00
d2959ddc3f Espresso testing support 2015-04-02 12:17:06 +00:00
ecb071ee88 Disable tunnel editing until ticket #815 is closed 2015-04-01 02:23:02 +00:00
624aa27e31 Updated README 2015-04-01 01:03:25 +00:00
2407e9be46 Use LocalBroadcastManager to broadcast router state within the app 2015-03-31 23:51:18 +00:00
50973b5c06 Bugfix 2015-03-31 23:50:20 +00:00
8165e49300 Make handler static to prevent leaks 2015-03-31 22:43:59 +00:00
77749dd7f9 Lint removal 2015-03-31 13:57:21 +00:00
ef7e4cf610 Updated translations 2015-03-31 13:37:44 +00:00
a7b2bf148b Missing strings 2015-03-31 13:36:40 +00:00
a2278179f9 Missing file 2015-03-27 01:49:59 +00:00
3b3bcb30da Save tunnel config in onStop() if possible 2015-03-26 23:59:10 +00:00
d78b68d285 Tunnel preference bugfixes 2015-03-26 21:52:58 +00:00
00de9e98d2 Revert change accidentally checked into f4375e1bcf14aed1c7a94628599bead3bf866844 2015-03-26 13:49:57 +00:00
a543280a56 Updated translations 2015-03-26 13:23:27 +00:00
f036544744 Support numeric values for ListPreferences 2015-03-23 00:45:14 +00:00
8aa9ce9303 Bugfixes 2015-03-23 00:44:38 +00:00
1c605c16cf Edit tunnel UI 2015-03-20 03:58:23 +00:00
fc7f703658 Missing new widget from previous commit 2015-03-19 21:37:11 +00:00
163ef0512b Preferences XML for tunnel properties 2015-03-19 21:36:49 +00:00
6709bebc6f EditTextPreference that updates its summary (from Bote) 2015-03-12 23:01:11 +00:00
c7fad6940a IntEditTextPreference: default summary, use default value 2015-03-12 23:00:08 +00:00
4b1ee639b7 Tweaks after upstream changes 2015-03-12 00:07:35 +00:00
d2fa17fa66 Updates for upstream changes 2015-03-11 04:07:40 +00:00
87e12846b3 i2ptunnel.jar doesn't depend on I2PTunnelGUI anymore 2015-03-11 01:10:24 +00:00
97d1367180 Use TunnelConfig now in i2ptunnel.jar 2015-03-10 20:52:59 +00:00
a0419c9eb7 Add support-v4-preferencefragment as a dependency 2015-03-09 01:56:40 +00:00
5191118b87 Updated CHANGELOG 2015-03-05 22:24:39 +00:00
f5214e4b99 Include priority for logged strings (per upstream) 2015-03-04 19:07:57 +00:00
9564855cce Rewrite LogWriter to use LogWriterBase from upstream 2015-03-04 12:15:22 +00:00
17ab043a4b 0.9.18
i2p.i2p tag: i2p-0.9.18
2015-03-04 12:13:45 +00:00
32b2b0ce75 Updated TODO with Silent Store checklist (useful reference) 2015-03-04 12:12:44 +00:00
b77e2ebbe5 Updated translations 2015-03-04 01:11:48 +00:00
9eeab68cdb String translation fix 2015-03-04 01:11:44 +00:00
96257015a9 Language cleanups 2015-03-03 23:37:56 +00:00
d7f6e3688c UDP and NTCP ports, part 4 2015-03-03 22:50:26 +00:00
5ef434e29f Updated translations 2015-03-03 13:23:16 +00:00
852d695dac UDP and NTCP ports, part 3 2015-03-03 13:21:32 +00:00
96cb8ab410 Bugfix 2015-03-03 00:19:36 +00:00
cd158cca84 Updated translations 2015-03-02 22:57:37 +00:00
b71a0a27d3 Fixed help Activity back and up nav 2015-03-02 22:57:03 +00:00
64268c7af8 Activity title fixes 2015-03-02 21:45:13 +00:00
95749f032e I2PTunnel -> Tunnels / Hidden Services Manager; string tweaks 2015-03-02 21:29:34 +00:00
0ac1ae56b0 Dropped unnecessary part of tunnels guide 2015-03-02 21:17:43 +00:00
c52bc45910 Client library 0.5.1
i2p.i2p tag: i2p-0.9.18
2015-03-02 21:16:57 +00:00
064ebc6857 Moved missing class into client library 2015-03-02 11:03:02 +00:00
6352cd9412 Client library 0.5
i2p.i2p tag: i2p-0.9.18
2015-03-02 00:51:30 +00:00
db6c74b4b8 Updated translations 2015-03-02 00:02:40 +00:00
a8d699bea2 (Hopefully) prevent NPE 2015-03-01 23:49:13 +00:00
5d9cb0029f Fixed style API bug 2015-03-01 23:28:11 +00:00
51f7fca1ea Updated translations 2015-03-01 11:08:55 +00:00
c61ccd32ba UDP and NTCP ports, part 2 2015-02-28 12:36:59 +00:00
1debd64bc3 Pull property definitions into one location 2015-02-28 10:19:42 +00:00
561bcfe3fa New translations 2015-02-28 04:59:15 +00:00
1c1f77f5c5 Updated translations 2015-02-28 04:58:27 +00:00
420526a7c4 Updated changelog 2015-02-28 04:57:10 +00:00
99496be412 Settings options for UDP and NTCP ports 2015-02-28 04:49:18 +00:00
b6f1cdc769 Enable Java 1.7 features 2015-02-28 00:46:49 +00:00
75e4153f4e Fix stringOptionsRequiringRestart handling; add NTCP and SSU port parsing 2015-02-27 23:28:01 +00:00
eefa5b8064 Prep work for supporting removal of router.config properties 2015-02-27 22:52:39 +00:00
bae8c7ec00 propagate from branch 'i2p.android.base' (head 2cc736f12cfa9d56a7df3ab4be399cb256cbfc2c)
to branch 'i2p.android.base.zzz.graceful' (head deb95d5a40b64c460483f1e5af1a5624ff95fa6f)
2015-02-27 11:04:12 +00:00
9d32e44547 Logger: Configurable flush interval
From i2p.i2p rev 2f451e3579eb5df2d316c1b507950d119d59a8da
2015-02-27 11:04:03 +00:00
80c8069769 propagate from branch 'i2p.android.base' (head 473e458dab49137f8211dcad60554cd90078807d)
to branch 'i2p.android.base.zzz.graceful' (head 57a30f761ea3d230301347dbb8b79bd3540f97a2)
2015-02-27 10:49:50 +00:00
42a0d552c7 Fixed potential leak 2015-02-27 10:45:42 +00:00
65848dd22b Fix NPE in AddressEntryLoader 2015-02-27 10:20:00 +00:00
610de188a4 Downgrade to Gradle tools 1.0.1
1.1.* cause "Cannot resolve symbol" errors in Android Studio, even though Gradle
can build the app.
2015-02-27 09:50:34 +00:00
f1cd3032c5 Updated FAB library 2015-02-27 01:54:29 +00:00
1a922ba04a Updated build tools 2015-02-27 01:54:17 +00:00
bdd59734ec Updated Android support libraries 2015-02-26 23:34:49 +00:00
fe162e4f5c Updated Android Gradle build tools 2015-02-26 23:34:31 +00:00
69e30e97b8 Updated TODO 2015-02-26 23:33:53 +00:00
zzz
5b4b151079 Preliminary support for graceful shutdown.
New buttons not yet implemented.
Untested.
2015-02-08 00:02:56 +00:00
513fbe0c9f Updated translations 2015-01-15 10:34:13 +00:00
de23a76e6b Option to start I2P on boot 2015-01-15 10:31:25 +00:00
c9936894d8 Updated TODO 2015-01-15 05:47:01 +00:00
cb6efd9ed8 Updated translations 2015-01-01 23:18:19 +00:00
ad245003bf Updated browser config instructions for Firefox and Orfox 2014-12-29 11:40:04 +00:00
9f27aedc49 Added postman's tunnels to default i2ptunnel.config 2014-12-27 21:43:00 +00:00
d8f883dce8 Feedback from zzz 2014-12-25 09:48:26 +00:00
7db1fbac94 Updated translations 2014-12-23 23:39:13 +00:00
19464124d6 Fixed NPE on browser refresh 2014-12-23 22:30:04 +00:00
5ba616facc Updated TODO (thx for testing zzz) 2014-12-22 00:22:54 +00:00
590a8183aa Fix settings menu Intents in debug build 2014-12-17 02:42:13 +00:00
9a45bbd18c 0.9.17.1
i2p.i2p tag: i2p-0.9.17
2014-12-14 05:15:16 +00:00
c5eedc0a5e Updated translations 2014-12-14 04:28:50 +00:00
715302c813 Upgraded floatingactionbutton to 1.3.0, dropped unused cardview-v7 2014-12-14 04:26:38 +00:00
3327ed722a I2PTunnel: Ask to install app for tunnel, if none are installed when opening 2014-12-12 13:59:02 +00:00
40afd69a54 Enable debug versions to be installed alongside release versions 2014-12-12 13:55:31 +00:00
241381c7fa Fix copy tasks to run every time 2014-12-12 13:17:48 +00:00
7a7ba093db Added CHANGELOG 2014-12-12 06:03:41 +00:00
f0e6744760 Prevent NPE 2014-12-10 12:03:34 +00:00
99002c1c5c Updated translations 2014-12-10 11:51:41 +00:00
605a6c1cf4 Bumped support-v4 dependency in client library to 21.0.2 2014-12-09 21:43:26 +00:00
954a9cc46b Bumped gradle plugin to 1.0.0 2014-12-09 11:56:19 +00:00
1ef838b966 Fixed NPE 2014-12-04 06:56:52 +00:00
c672ca05f5 0.9.17
i2p.i2p tag: i2p-0.9.17
2014-12-02 00:40:17 +00:00
c493e73889 Client library 0.4
i2p.i2p tag: i2p-0.9.17
2014-12-01 08:40:48 +00:00
2b7c280f5b Tunnel details 2014-12-01 08:39:25 +00:00
23eab8a90a FloatingActionButton for I2PTunnel and addressbook 2014-12-01 03:57:21 +00:00
c59103eb76 Comments 2014-12-01 03:14:55 +00:00
f00a35ee09 Move versionCode and versionName definitions to build.gradle 2014-11-30 21:24:47 +00:00
af93725c01 Binding for Services 2014-11-30 11:35:15 +00:00
3953301c57 Fix intent generation 2014-11-30 10:55:34 +00:00
2dab9d5d4f Upgraded support libraries to 21.0.2 2014-11-27 11:42:48 +00:00
b77666fa26 New client library translations for ro 2014-11-25 11:44:46 +00:00
eca931c1b5 Updated translations 2014-11-25 11:44:22 +00:00
86ae77701f 0.9.16-rc1
i2p.i2p: cb66382d9716f7d9cd9441830face910705253e0
2014-11-25 04:48:48 +00:00
c1ee0c4d9e Updated Gradle Witness
Source: https://github.com/WhisperSystems/gradle-witness
Git commit: 10f1269c0aafdc1d478efc005ed48f3a47d44278
2014-11-25 03:51:52 +00:00
e632b35862 Bind to IRouterState using explicit intents (required for API 21 and up) 2014-11-16 18:49:11 +00:00
20d2dcd891 Updated translations 2014-11-16 03:06:29 +00:00
61d5ba5a7c Toolbar back navigation 2014-11-10 11:59:54 +00:00
339f688b7c I2PTunnel help 2014-11-10 11:55:33 +00:00
fed11e703a Don't start new activity if we are already there 2014-11-10 11:33:36 +00:00
438df8142a Settings back navigation 2014-11-10 11:27:11 +00:00
7b3730be24 Updated i2ptunnel.config, dropped Telecomix and Nameless 2014-11-10 10:43:11 +00:00
d6c20bafb3 Material design: one-line text lists 2014-11-10 10:42:05 +00:00
b8998db3a3 Revert to general Intent for selected addressbook entries 2014-11-10 05:39:48 +00:00
9ab1c84878 Material design: settings 2014-11-10 03:41:49 +00:00
2f3335d361 Material design: list items 2014-11-10 02:30:00 +00:00
0e8d900ed4 I2PTunnel secondary action to open linkable tunnels 2014-11-10 01:56:35 +00:00
2c7ce0b7c6 News fragment margins 2014-11-09 12:24:59 +00:00
3e2bdacf89 Material design: I2PTunnel list 2014-11-09 12:13:32 +00:00
64c32076a1 Show correct browser config activity 2014-11-08 07:50:50 +00:00
4d75ce7de1 Missing files 2014-11-08 07:50:30 +00:00
269ae2f569 Help details back nav in toolbar 2014-11-08 06:46:04 +00:00
a42a4b2c99 Reworked help navigation 2014-11-08 03:41:14 +00:00
96f5c2b488 Change property names to match original script (better interop with other repos) 2014-11-08 03:40:48 +00:00
09ab9779aa Addressbook doesn't use two columns yet 2014-11-05 00:50:55 +00:00
97ed0a3a8f NPE fix 2014-11-04 12:18:56 +00:00
ec6d225dc6 Icons for uninstalled supported browsers 2014-11-04 12:07:41 +00:00
800a332691 Swap Orfox with Firefox in supported browsers (no official Orfox release yet) 2014-11-04 12:02:02 +00:00
45eae17561 Simplify browser list UX 2014-11-04 11:54:22 +00:00
092365cab2 Help twopane view 2014-11-04 03:06:03 +00:00
c98c2f101d Updated TODO 2014-11-04 02:46:20 +00:00
8e86634a41 Recommended browsers, Orfox config, link to app store from browser list 2014-11-04 02:43:34 +00:00
7424e5b707 Browser configuration guides for embedded, Orweb, unknown and unsupported 2014-11-04 01:03:59 +00:00
5175c937a9 Help: List browsers, detect ones we know can (not) be configured for I2P 2014-11-03 12:26:58 +00:00
2692a567ab BetterAsyncTaskLoader from Bote 2014-11-03 12:23:24 +00:00
2de971fb52 New translations 2014-11-03 11:46:17 +00:00
403b2e8cd9 Material design: toolbar for help screen 2014-11-03 03:22:29 +00:00
22141e723a Fix back button when drawer indicator disabled 2014-11-02 23:20:13 +00:00
419758125e Updated translations 2014-11-02 12:42:44 +00:00
6db307c681 Client library 0.3
i2p.i2p tag: i2p-0.9.16
2014-11-02 01:46:33 +00:00
cdec6d2f98 Updated README 2014-11-02 01:36:11 +00:00
18a6dc9719 Use mavenLocal() repository instead of a local file repo 2014-11-02 01:35:55 +00:00
fb92d7858a Upgraded build tools to 21.0.2 2014-10-30 23:10:36 +00:00
b8e005bdd5 Copy tasks can only have one destination directory 2014-10-30 23:10:00 +00:00
6568489b27 Upgraded gradle wrapper and build tools 2014-10-30 12:15:49 +00:00
3ff3e14fc2 New Gradle Witness build
Includes a fix for Gradle 2.+ that is not yet pulled in:
https://github.com/WhisperSystems/gradle-witness/pull/3
2014-10-30 12:15:04 +00:00
092591f4ec Material theme: toolbar
Navigation tabs have all been replaced with spinners, because there are no nice
tab libraries for API 9+.
2014-10-29 11:47:53 +00:00
895a3a1dcc Fix deprecation 2014-10-27 21:37:52 +00:00
dc4ce3f8c7 Updated translations 2014-10-27 07:31:36 +00:00
7e3b9d5065 Updated translations 2014-10-22 21:15:55 +00:00
e58ffc9fd4 Material theme: nav drawer 2014-10-19 06:48:33 +00:00
0d029988c3 Material theme color definitions 2014-10-19 06:05:08 +00:00
6f01989a4b Nav drawer icon changed 2014-10-19 06:04:07 +00:00
19aeaec406 New material theme menu icons 2014-10-19 06:03:14 +00:00
b4d1241a9e Implement material styles properly with appcompat 2014-10-18 21:08:33 +00:00
fef4aa0e86 Initial migration to API 21 2014-10-18 06:11:39 +00:00
766266b147 Updated translations 2014-10-18 02:36:53 +00:00
93410c07bb Updated ignores 2014-10-17 08:02:24 +00:00
dc27a782b0 Fixed NPE in client library with State unparceling
This only fixes the symptom, but the crash log on Google Play doesn't shed light
on what the problem is.
2014-10-17 06:54:26 +00:00
zzz
b633df73c0 Update imports for move of router data structures 2014-10-16 23:10:32 +00:00
41d1200df7 0.9.15.1 2014-10-16 10:47:27 +00:00
c9a62fba9a Drop interface that isn't in source yet 2014-10-16 10:25:12 +00:00
c9598fa831 Update default subscriptions 2014-10-16 10:16:06 +00:00
9965c31b2d Pass dir and file to Util.mergeResourceToFile() in the correct order 2014-10-16 10:15:13 +00:00
43de6425b2 New translations for client library 2014-10-16 10:13:56 +00:00
b98cdf3e0b 0.9.15 2014-10-16 04:32:52 +00:00
26c11ebaed Updated translations 2014-10-16 04:30:49 +00:00
c9c3de1d04 Updated Androidplot to 0.6.1 2014-10-16 04:30:25 +00:00
9b29b56982 Upgrade support libraries to v20.0.0 2014-10-15 22:19:58 +00:00
8956fd7ce0 Updated translation strings 2014-10-15 21:52:46 +00:00
f1f94ea3e0 New translations 2014-10-15 21:49:32 +00:00
94b433aead Padding fix 2014-10-15 21:31:27 +00:00
5623d54114 Basic help page with HTML content 2014-10-15 21:21:11 +00:00
9133a2f848 Prepare for material theme 2014-10-15 10:36:55 +00:00
70324c3ecc Missing class 2014-10-15 02:50:37 +00:00
5613d21324 Replace first start warning dialog with browser config dialog 2014-10-14 11:15:14 +00:00
9036bf3f6b Re-enable (blank) help activity 2014-10-14 11:14:28 +00:00
94991d2df3 New type and status styles for tunnel list 2014-10-07 06:22:09 +00:00
76f23946f0 Don't show dialog on new version until we have something new to tell them 2014-10-05 04:12:29 +00:00
862e856913 Fixed graphs settings page notice when router not running 2014-10-05 03:38:17 +00:00
cf3de34cb6 Removed "participating" option from settings UI
Temporarily hidden because of problems with changing defaults. We don't want to
cause network instability because we have thousands of new unstable routers
churning the network, and can't change their default settings.

In future, we should look at the user's network and settings, and try to have 
"smart" defaults based on a user-chosen profile, or based on net type - see
Connectivity.isConnectionFast().
2014-10-05 02:59:18 +00:00
81de79c725 Remove "enable I2CP" from settings UI
Android apps should use the client library to connect to I2P Android. The TCP    
interface would only be useful if using the Android device as a gateway or 
dedicated router, but it would need to listen globally, not locally. If someone
asks for that in future, we can add this back and improve the configuration.
2014-10-05 02:53:09 +00:00
228b6f1baa Comment out disabling UPnP until other changes are finished 2014-10-05 01:17:31 +00:00
8e395cfd4e Detect more config options requiring restart 2014-10-05 01:16:17 +00:00
6d6123df9b Inform user that changing uPnP setting will require a router restart 2014-10-03 01:09:32 +00:00
321a49156c Display news in TextView instead of WebView 2014-10-03 01:04:26 +00:00
e8a47e17fb Developer note in default router.config 2014-10-02 11:02:06 +00:00
9df27ea168 Move property file writing methods to Util 2014-10-02 04:06:17 +00:00
cb6b7c4f48 getRouterContext() helper function 2014-10-02 03:55:24 +00:00
ca623e6b18 New class for connectivity checks
Source: https://gist.github.com/str4d/22cac7a3f70bc227cdca
License: http://opensource.org/licenses/MIT
2014-10-02 02:02:26 +00:00
8b6e02136e Fix TextResourceDialog scrolling 2014-10-01 21:06:07 +00:00
6a0493a578 Updated release notes 2014-10-01 20:58:22 +00:00
bf2a437a82 Initial news space fixes 2014-10-01 20:47:02 +00:00
ac949e3f5e Don't use wide view for news webview 2014-10-01 10:50:09 +00:00
7483251393 Copy log entries to clipboard, tweak copy text 2014-10-01 10:31:28 +00:00
d690b7d861 Copy logs to clipboard 2014-10-01 10:23:36 +00:00
829695d690 Move licenses menu option to about dialog button 2014-10-01 03:31:18 +00:00
05c2dbd388 Javadoc improvements
This is the client library 0.2 commit
2014-09-29 23:54:28 +00:00
c8e1643326 New client library translations for ru 2014-09-29 23:53:48 +00:00
d72c936a0e Fix addressbook settings header Intent bug 2014-09-29 23:52:59 +00:00
06d4d7d10d New client lib translations for fr 2014-09-29 04:08:30 +00:00
b506b5e740 New translations for client library 2014-09-26 23:19:38 +00:00
2d65bd373c Configure Transifex for client library strings 2014-09-26 12:28:15 +00:00
7c869adf58 Client library helper class
Based on OrbotHelper from libnetcipher, with logic from i2p.i2p-bote.android
2014-09-26 12:27:51 +00:00
61a7566007 Client library 0.2
i2p.i2p tag: i2p-0.9.15
2014-09-25 05:00:28 +00:00
9d42901079 Updated translations 2014-09-25 04:57:47 +00:00
fb31818a3c 0.9.14.1-rc3 2014-08-23 01:24:52 +00:00
6355214b5f Updated translations 2014-08-23 01:01:37 +00:00
d5c0704477 Updated TODO 2014-08-23 00:45:18 +00:00
411131b8a6 Updated translations 2014-08-23 00:45:04 +00:00
10ed266d2c Clarify property inversion 2014-08-23 00:25:40 +00:00
bccfe03b5d Sync settings XML defaults with router.config defaults 2014-08-22 23:30:35 +00:00
a44ac8a45c Another domain name fix 2014-08-22 14:02:23 +00:00
5610752c6d Updated old domain names 2014-08-22 14:01:19 +00:00
7047913b45 Updated translations 2014-08-22 13:57:33 +00:00
a41aa79920 Explain to users that graphs need to be configured 2014-08-22 13:22:38 +00:00
4fcc1121b7 Padding tweak 2014-08-22 12:09:55 +00:00
514aa51224 Fixed images in peers WebView 2014-08-22 11:50:53 +00:00
0c46dc9bd0 Display auto-start setting in I2PTunnel details 2014-08-22 11:27:11 +00:00
4b7f951e32 0.9.14.1-rc2 2014-08-21 14:37:40 +00:00
a58a9d7540 Fixed "called on wrong thread" issue in browser 2014-08-21 14:33:39 +00:00
d3eaebd324 Temporary fix for a settings bug 2014-08-21 13:40:59 +00:00
37c366a528 Client library 0.1.1 2014-08-21 10:55:32 +00:00
8f6289984b 0.9.14.1-rc1 2014-08-20 11:19:33 +00:00
7629bb54ce Feature graphic for Google Play 2014-08-20 06:41:41 +00:00
ee7d227990 Fixed sq translation 2014-08-20 04:34:44 +00:00
4cc940c995 Updated TODO 2014-08-20 04:28:19 +00:00
2336eebdd0 New translations: sq U id 2014-08-20 04:02:50 +00:00
62035050c5 Padding tweak, prevent status headings flicker when opening app 2014-08-20 03:37:20 +00:00
6775d57c22 Rearranged nav drawer, only show graphs peers and netdb for advanced users 2014-08-20 03:02:29 +00:00
d3a1910b2e Return null on unknown State in Parcel 2014-08-20 02:37:27 +00:00
aa43d960dc Revert AndroidManifest.xml package name change
Changing this requires changing the import path of the generated R file, which
will unnecessarily affect most of the code. R is built in a package based on
the AndroidManifest.xml package name, whereas the final app package name is
overridden in app/build.gradle for free and donate versions.
2014-08-18 14:50:54 +00:00
2e3047274e Added legacy flavor with old app ID, fixed package names in manifests 2014-08-18 13:11:47 +00:00
a3cef11e08 Updated ignores 2014-08-17 23:13:23 +00:00
543fb51d76 Simplify adding client library to a local flatDir repo 2014-08-15 01:41:16 +00:00
4328db1908 Fixed source and javadoc jar creation 2014-08-07 13:25:02 +00:00
69fbb5dc92 Updated translations 2014-08-07 06:57:43 +00:00
0c5d8f8e9e Moved jbigi to client library, so clients get benefit 2014-08-06 22:45:06 +00:00
b88e150803 Final changes, x86 and MIPS libjbigi.so now build
Untested, we need to find x86 and MIPS devices to test with.
2014-08-06 10:52:22 +00:00
35fe44fc59 Initial x86 and MIPS support (not enabled, missing --host parameters) 2014-08-06 00:08:03 +00:00
464adb9e71 More non-ABI-specific script 2014-08-05 23:31:01 +00:00
66d370abeb Reorganized to run non-ABI-specific parts of script first 2014-08-05 23:25:10 +00:00
11aded07ca Refactored to pull out ABI-specific settings 2014-08-04 01:29:04 +00:00
5d0861e22e Only allow numbers in I2PTunnel wizard port fields (ticket #1331) 2014-08-03 21:18:05 +00:00
5778eb9d1c Updated translations 2014-07-26 02:44:45 +00:00
0e47bc5042 Updated translations 2014-07-19 23:26:55 +00:00
8f9a6922ad Bugfixes 2014-07-17 06:40:17 +00:00
05cc0634b7 Updated README 2014-07-17 06:31:04 +00:00
583666695c Improved Maven upload code, collected parameters in gradle.properties
maven-push.gradle source:
https://github.com/chrisbanes/gradle-mvn-push
2014-07-17 06:29:40 +00:00
e67ba59e51 Uploading to the Central Repository 2014-07-17 05:41:57 +00:00
ab619f904d Reverted to Enum for State, make it Parcelable
Talked with zzz and did more research, the overhead of Enum is minimal compared
to the benefits it provides.
2014-07-17 00:56:04 +00:00
f2f7418c8b Ignore old Enum state 2014-07-16 05:26:44 +00:00
23c55d50fb Remove I2CP port starting, clients can use domain sockets now 2014-07-16 04:20:40 +00:00
e0acb322a5 Lint 2014-07-16 03:47:17 +00:00
2a1427054d Use static int constants for State instead of Enums
Enums often require more than twice as much memory as static constants, and
should be strictly avoided on Android.
https://developer.android.com/training/articles/memory.html#Overhead

The advantage of this change is that client library users can directly compare
the status values they get from IRouterState to the constants, instead of
parsing a string representation of an Enum.
2014-07-16 01:28:49 +00:00
d878d2d8a4 Moved AIDL interfaces to client library 2014-07-16 01:25:19 +00:00
5386829edf Implemented I2CP connections over Android Local[Server]Sockets 2014-07-15 13:03:33 +00:00
5d74e7ffef Comments 2014-07-15 11:13:55 +00:00
332ec1e0ad Corrected client library package group 2014-07-15 11:08:39 +00:00
060262ee52 Separated client library version 2014-07-15 10:16:07 +00:00
c75fe55e56 Updated client library package 2014-07-15 08:56:41 +00:00
bccf5e0965 Bundle I2P libs in client AAR 2014-07-15 06:19:31 +00:00
6bd905a027 Fixed client building (thanks alkemist from Freenode#gradle) 2014-07-15 03:40:47 +00:00
fc0b393b14 Markdown fix 2014-07-15 01:52:59 +00:00
f2acde73fe Specify full version for Android Gradle plugin so script compiles when offline 2014-07-14 12:39:32 +00:00
77a7f5f603 Client library AndroidManifest.xml 2014-07-13 21:56:26 +00:00
d235da093f Build script for client library 2014-07-11 05:18:46 +00:00
795d3ab314 Updated translations 2014-07-07 02:58:49 +00:00
dd40931a23 Correct version code for current release
If we move to increment-by-one instead of calculating the version code, we need
to keep the code above the current highest generated code, so that F-Droid will
order new builds correctly above old builds.

If we decide to go back to calculating version codes later, it is possible - we
can increment the version code 2047 times before it will clash with the one for
0.9.14.
2014-07-07 02:43:16 +00:00
8b71e4fc2e Fixed CacheProvider authorities clash 2014-07-07 02:38:51 +00:00
ed61f0414e Padding tweak 2014-07-07 01:24:57 +00:00
06ef95c7ac Better layout padding for main view 2014-07-07 01:14:58 +00:00
2936bfc2b7 wrap long lines 2014-07-04 00:41:33 +00:00
9c655ffebf move from cat|grep|sed to awk 2014-07-04 00:41:17 +00:00
9d8fb684d2 Fixed building armeabi libjbigi.so 2014-07-03 22:36:20 +00:00
0d744e269c Static verification of remote dependencies using Gradle Witness
https://github.com/WhisperSystems/gradle-witness
2014-07-01 05:06:48 +00:00
36ffb6eda4 Default to ../i2p.i2p for copying I2P resources 2014-07-01 04:54:19 +00:00
df7ee4bd05 Updated README 2014-06-29 02:58:32 +00:00
d98d6abff3 Updated android gradle plugin version 2014-06-29 02:33:47 +00:00
260cc8a5a2 Updated gradle specified in wrapper to 1.12 2014-06-28 23:42:13 +00:00
zzz
a0a1df8093 Remove local copies of I2P Secure* utilities since we are
now on Java 6 (API 9).
2014-06-28 15:37:59 +00:00
e4110eb894 Signing instructions 2014-06-28 04:21:45 +00:00
d0264bf475 Addressbook license is copied over, should not be checked in 2014-06-28 02:04:12 +00:00
7ec8b0a592 Copy I2P resources 2014-06-28 02:03:39 +00:00
e3ecac8fec Rquirement: Android Support Repository 2014-06-28 00:40:02 +00:00
4fdc7940dd Version 2014-06-27 17:26:04 +00:00
9920ad34cd Renamed package to net.i2p.android, added free and donate flavors 2014-06-27 17:08:44 +00:00
8a7025038a New translations 2014-06-27 16:40:17 +00:00
a47c80df8c Updated Transifex config 2014-06-27 16:40:10 +00:00
a1a5aeaf6c Don't abort on lint errors 2014-06-27 16:34:01 +00:00
3a8eeabe3e Improved handling of signing keys 2014-06-27 16:29:43 +00:00
3e34bac295 Updated README 2014-06-27 16:19:36 +00:00
66d0dce40c Dropped Eclipse files 2014-06-27 15:55:20 +00:00
c8d3ee7aac Updated ignores 2014-06-27 15:51:55 +00:00
959537adc2 Reflowed AndroidManifest.xml, added missing parent meta-data for below API 16 2014-06-27 15:46:34 +00:00
7ca050fdf5 Cleaned up routerjars 2014-06-27 15:20:58 +00:00
07130abf23 Update min API to 9 (Gingerbread) 2014-06-27 15:15:15 +00:00
ba82d59b89 merge of '3a37cc3435714b0ed267c4f01159b77e1e59cd87'
and 'b1d84ce6fa493115c9ddc12b5a5a5a62c4dadee6'
2014-06-27 15:13:02 +00:00
8819dc5f30 Build scripts 2014-06-27 15:11:49 +00:00
zzz
a034b78dfd Update min API to 9 (Gingerbread).
API 8 (Froyo) is now less than 1% of active devices, and
these devices are generally too low-powered to run I2P effectively.
This allows us to move the remaining I2P jars to Java 6.
2014-06-27 13:52:29 +00:00
2dc56d57d4 Reorganized files 2014-06-27 11:42:20 +00:00
3aff1c4f75 Added gradle wrapper 2014-06-27 06:56:12 +00:00
55509adda6 Ask user to enable I2CP if app requests start with I2CP disabled 2014-06-20 02:33:22 +00:00
zzz
19def413c1 Make shared clients (IRC) delay-open since the HTTP proxy
is not a shared client any more.
2014-06-14 11:31:27 +00:00
63a0e2117f Copy i2ptunnel.config from resource for new installs 2014-06-12 23:17:05 +00:00
21e0b2a667 Listen for START_I2P action 2014-06-03 23:49:10 +00:00
6ce15e27de Updated translations 2014-06-02 22:08:00 +00:00
7a0a56373d Fixed remote usage of IRouterStatus 2014-06-02 21:40:23 +00:00
37da05ca98 Pull in changes to defaults while maintaining user settings 2014-06-01 02:50:15 +00:00
f003bbbfa4 Fixed settings parsing 2014-05-31 23:56:30 +00:00
zzz
a6978bb161 fix net status text 2014-04-27 18:59:34 +00:00
b1ec76de5a Missing change in landscape layout 2014-04-18 11:28:36 +00:00
dc58796c97 Updated TODO 2014-04-09 02:03:38 +00:00
c7075c3fc4 Pull API level from routerjars/AndroidManifest.xml.in 2014-04-09 02:03:25 +00:00
8d4f1b174d Check routerjars/local.properties for NDK dir (ticket #1032) 2014-04-09 01:52:39 +00:00
6f29991829 Updated translations 2014-04-08 12:38:39 +00:00
a414b10ce8 Long press to start/stop router (prevents accidental presses) 2014-04-08 01:12:52 +00:00
030fc60445 Updated i2ptunnel.config from upstream 2014-04-08 00:47:52 +00:00
9d215353e8 Updated release notes 2014-04-08 00:27:29 +00:00
f0a2166ae0 Correct Android forum URL 2014-04-08 00:27:17 +00:00
af097474de Padding 2014-04-08 00:25:43 +00:00
5a1fc32da4 All logging is conditional 2014-04-07 23:53:52 +00:00
7218b79643 Hide Help page until it is finished 2014-04-07 22:48:40 +00:00
ccd5ae45df Removed old translations that have no default string 2014-04-07 22:47:47 +00:00
3584890277 HTTP client tunnel bug disappeared...? 2014-04-07 22:27:47 +00:00
4e17010f59 New translations for nb 2014-04-07 21:22:50 +00:00
aa8009cb70 Updated translations 2014-04-07 21:22:03 +00:00
30d1816c43 Updated translations 2014-03-31 21:25:04 +00:00
02c25ba174 Updated translations 2014-03-16 21:50:14 +00:00
8f8bbcb19f Show "router not ready" message on graph settings page 2014-03-16 10:50:00 +00:00
696ae2bfc0 Prevent graph settings being wiped just after router start 2014-03-15 21:33:31 +00:00
0ea468ea71 Updated TODO 2014-03-15 12:37:40 +00:00
a2d9adb071 Implement required API 19 method override 2014-03-09 09:36:36 +00:00
b8cc64d4ea Updated translations 2014-03-07 21:48:14 +00:00
6e3e99c62f Fixed unzipping resources 2014-03-04 03:28:05 +00:00
84a63cc911 Updated TODO 2014-03-04 03:14:43 +00:00
b8c3b7e3df merge of '1af98386e3efe227cfa78be17a8c4a7785aeea8a'
and '87e720357989c67de7752f3616a85c09b5975eda'
2014-03-04 03:07:32 +00:00
04e190d2d0 Updated translations 2014-03-04 03:07:23 +00:00
ce2a762db2 Temporarily enable inbuilt browser until an alternative is ready 2014-03-04 03:00:13 +00:00
76014f2081 Move hardcoded strings to strings.xml 2014-03-03 22:01:26 +00:00
6d570646f1 Build addressbook.jar instead of addressbook.war (no Jetty dependency) 2014-03-03 03:50:23 +00:00
104c17cb9c Dropped unnecessary drawables 2014-03-01 21:32:49 +00:00
0860ee83b0 propagate from branch 'i2p.android.base' (head 76695da8c3e4ec8e9a3034baa1d60babbef279ac)
to branch 'i2p.android.base.fragments' (head 912c186827c29b8237045712fb25b7500a994fb3)
2014-03-01 21:29:51 +00:00
zzz
707c0e9aa8 url updates 2014-03-01 16:19:02 +00:00
163bc2ce58 only build router once 2014-02-27 02:58:59 +00:00
cec1f8fe52 propagate from branch 'i2p.android.base' (head 13ee72756c9d5407dafed769cda7c7dd5bd66cce)
to branch 'i2p.android.base.fragments' (head 29d48eb27592b749718622a683371d10f07431b2)
2014-02-27 01:57:16 +00:00
97f52f8139 remove res/raw/license_fatcowicons_txt in the clean target 2014-02-27 01:45:00 +00:00
0094cc5637 Only build the router.....when building (ticket #1214) 2014-02-27 01:31:52 +00:00
01c994e7b2 fix tag 2014-02-27 01:30:07 +00:00
b60ae00fd7 19 isn't a valid target anymore; it's now android-19 2014-02-26 23:50:28 +00:00
2340500083 Updated Irc2P servers 2014-02-24 01:14:11 +00:00
zzz
a91261f5ca JNI Build:
- Update JNI build script to use GCC 4.6, required for
   NDK r9b and higher (ticket #1202)
 - Add more checks in build script, to fail quicker on problems
 - Move default NDK build location up one directory level
 - Fix setting JAVA_HOME
 - Fixes for running script in the directory

 I did not replace the checked-in 4.4.3 libjbigi.so with
 the new 4.6 version, as there's no need.
 The 4.6 version will be tested by nextloop in the F-Droid build.
2014-02-23 16:40:47 +00:00
a145729353 Updated .mtn-ignore 2014-02-21 06:32:00 +00:00
0270444a94 Added missing drawables 2014-02-21 06:12:11 +00:00
01e22f4fb5 Updated TODO 2014-02-21 00:14:21 +00:00
2e892841cb Display text from initial news when router is first started 2014-02-21 00:14:07 +00:00
e271dc90ae Removed License button from VersionDialog 2014-02-21 00:13:03 +00:00
77ced0bd1f Revert to RouterLaunch.main(null) (upstream fixed) 2014-02-20 23:06:42 +00:00
ac67533ef2 Moved classes around 2014-02-20 21:42:25 +00:00
305c834aa1 Updated TODO 2014-02-20 11:18:02 +00:00
cbb2973b36 Linkify .i2p URLs 2014-02-20 10:35:22 +00:00
f360ab4d5d Updated RouterService with changes to RouterLaunch.main() 2014-02-20 06:38:51 +00:00
296d21d1d0 Added notifications for clients; notify on news update 2014-02-20 06:36:57 +00:00
a5dd751227 Register shutdown hook in NewsFetcher 2014-02-19 07:16:01 +00:00
d47bdf85d2 New translations 2014-02-07 04:30:04 +00:00
c2a33541b3 Updated translations 2014-02-07 04:28:55 +00:00
3e46d98481 Use the tunnel type ImageView to show tunnel status 2014-01-27 06:18:11 +00:00
124b1499b4 Show list of running tunnels 2014-01-27 06:02:12 +00:00
2d8529e691 Show full router stats as advanced option 2014-01-27 03:37:57 +00:00
4a135023b9 Layout id renaming 2014-01-27 02:51:48 +00:00
e9fbe8c2ef Removed website and FAQ links from nav drawer, added website link to about 2014-01-27 02:31:10 +00:00
228d0204fc Updated TODO 2014-01-26 06:47:32 +00:00
0ebecd9b64 Changed navbar first-use mechanics to match Android design pattern 2014-01-26 06:38:34 +00:00
c062a0f803 Missing change in layout-land 2014-01-26 05:35:46 +00:00
4f24517de9 Hide status ScrollView if router not running 2014-01-26 05:12:45 +00:00
d8636ff563 Added About dialog 2014-01-26 04:59:16 +00:00
4e57f78931 Disable WebActivity from receiving URL Intents 2014-01-26 00:19:17 +00:00
ccbbc3c368 Added link from Log Activity to Logging settings
TODO: Fix link for Preference headers
2014-01-25 20:21:11 +00:00
b9374b5ead Manually set state to WAITING 2014-01-22 15:18:14 +00:00
ceaff935d6 New status image for "no internet" 2014-01-21 07:00:01 +00:00
255702b6bb Updated TODO 2014-01-21 06:52:31 +00:00
2e68aa900b Change to RI tab when viewing RI from LS 2014-01-17 14:57:47 +00:00
67eddba621 Display current default log level in settings 2014-01-17 00:17:54 +00:00
29fb1f0689 Added missing file 2014-01-16 23:56:59 +00:00
de605a1d5b Removed unused import 2014-01-16 23:48:31 +00:00
41b49b7bc8 Call onRouterBind() of detail fragment in two-pane mode 2014-01-16 23:47:49 +00:00
c691a11c3d Added scrolling to RI and LS detail fragments 2014-01-16 19:29:02 +00:00
2f6b1189ae Hide edit action in I2PTunnel 2014-01-16 19:10:33 +00:00
a7a328238b Added TODO list 2014-01-16 18:15:46 +00:00
c9c31ccb76 Stubbed out code for log-level overrides 2014-01-16 18:05:01 +00:00
8457f279f2 Moved debug log entries to Util.d() 2014-01-16 17:40:14 +00:00
fbf353858e Added settings page for logging, config option for default log level 2014-01-15 17:45:40 +00:00
99ef07d1fc Added action to manually reload subscriptions 2014-01-15 02:40:20 +00:00
a5f5e97e37 Use new ClientApp interface to start/stop BOB 2014-01-15 02:04:36 +00:00
165bcc1c9d Removed temp changes 2014-01-11 23:41:02 +00:00
45efd6670f Adjusted labels 2014-01-11 23:40:07 +00:00
b601722b31 Removed temp changes 2014-01-11 23:39:48 +00:00
14e1a2dca1 Notify user if router not running when opening graph settings 2014-01-11 22:40:10 +00:00
d2385166cb Fixed race between router binding and onCreateOptionsMenu() 2014-01-11 22:30:12 +00:00
ed17d59896 Fix WebActivity handling of indirect Intents 2014-01-11 22:06:38 +00:00
55cfd455ca Use indirect Intents to open URLs 2014-01-11 22:06:09 +00:00
f207cf3116 Removed unneeded import 2014-01-11 21:07:42 +00:00
5825d1d2a5 Fetch router state in onResume() 2014-01-11 21:07:22 +00:00
zzz
077f9902a9 update to API 19 2014-01-11 17:36:45 +00:00
d77cbde3b3 Updated target API to 19 2014-01-05 06:00:57 +00:00
62ff63665f Fixed check for state on shutdown 2014-01-04 07:34:02 +00:00
1377aceb18 Use images derived from I2P logo for status 2014-01-04 07:31:59 +00:00
c7617ba856 Ensure state image is correct when router has shut down 2013-12-30 00:08:29 +00:00
08c6018483 Added State.ACTIVE to indicate when router is ready for use 2013-12-30 00:03:08 +00:00
f9f283409d Fixed out-by-one error in logs 2013-12-29 23:34:04 +00:00
4ef42cb462 Scrolling for log entries 2013-12-29 23:30:04 +00:00
65428dda8e Fix IRouterState Service connection leak properly 2013-12-29 23:29:49 +00:00
6311ab4b67 Comment to explain dual-pane criterion for preference headers 2013-12-29 19:22:31 +00:00
0c20a45207 Don't update filter list if content not loaded 2013-12-29 19:18:48 +00:00
6712148010 Fixed potential leak of IRouterState Service connector 2013-12-29 19:07:09 +00:00
07e6e293bc AIDL interface for getting router status, used for MainFragment status images
Other apps can use this interface by copying the AIDL files into their source
tree.
2013-12-29 18:04:39 +00:00
2dd1655e1e Type argument changes caused by i2p.i2p cleanup 2013-12-18 05:26:58 +00:00
97037fe1d8 New translations 2013-12-06 12:20:31 +00:00
d79f797558 Reorganized source files by section instead of by type 2013-12-06 07:56:09 +00:00
42649e02ea Replaced logo with status images, combined start and stop buttons 2013-12-06 02:20:32 +00:00
c6aeb79944 Created some status indication images for the main page based on I2P logo dots 2013-12-06 02:16:48 +00:00
fb66ec62d6 Improved IRC tunnel names/descs 2013-12-05 00:49:50 +00:00
15f1e18da1 Added Nameless IRC to default tunnels 2013-12-05 00:47:32 +00:00
15caf8a97c Reorder main fragment 2013-12-05 00:36:38 +00:00
44989a42f2 Remove bigText when replacing status 2013-11-25 20:29:44 +00:00
3867eb6fda Fixed positional substitution 2013-11-25 20:24:43 +00:00
0755e79b1a Status bar: Show status details as bigText, mirror router status in title 2013-11-25 20:23:31 +00:00
3a1e43b322 Use NotificationCompat.Builder 2013-11-25 11:33:39 +00:00
6a2d494921 Show full Log entry in a detail page 2013-11-19 11:39:55 +00:00
be14d65899 Renamed Log listitem layout, limit list to max 3 lines 2013-11-19 10:50:58 +00:00
c42dc725d6 Migrated LogFragment to use loader 2013-11-19 02:34:58 +00:00
83ab1d09ae Don't crash if RI or LS lookup returns null
Todo: determine why some lookups of OBEPs can be null, and handle the null case
in the UI.
2013-11-18 06:29:07 +00:00
af30dc8e24 Fixed addressbook crash 2013-11-18 06:10:35 +00:00
0268ce13ad Missing change to strings 2013-11-17 20:11:31 +00:00
5b897bc993 Show number of search results in addressbook 2013-11-17 20:09:16 +00:00
1fbf6b1b72 State that File Manager is needed 2013-11-17 12:13:40 +00:00
8095eed241 Added wizard page to read B64 from text file 2013-11-17 12:05:59 +00:00
8916c123ef Hide actions when router not running 2013-11-17 10:40:48 +00:00
b742dd8ee8 Use callback for TunnelListFragment 2013-11-17 10:27:05 +00:00
b02b446e46 Fixed addressbook crash when router not running 2013-11-17 06:26:32 +00:00
01b07fed5b Simplified callback system 2013-11-17 06:19:50 +00:00
2fa205daec Router-down fixes 2013-11-17 05:58:32 +00:00
3644d738ee Cleanups 2013-11-16 22:29:21 +00:00
c044c4de4d Use new callbacks for NetDbDetailFragment 2013-11-16 22:18:04 +00:00
c705527113 Handle corner case where onRouterBind() not called but RouterContext available 2013-11-16 22:13:39 +00:00
8b79af434e Use new callbacks for NetDB 2013-11-16 22:07:32 +00:00
2c8e9d62be Use new callbacks in AddressbookFragment 2013-11-16 21:57:33 +00:00
9832779a50 New callback system to make getRouterContext() reliable 2013-11-16 21:56:10 +00:00
9f535a3260 Save data from add wizard in private book
This does nothing. getRouterContext() is always null when onActivityResult() is
called. Todo: implement callback that fires when the Router has bound and the
Fragment has an Activity.
2013-11-16 10:10:22 +00:00
0beaec366f Use ns.getEntries() to get both name and Dest, and for filtering 2013-11-15 20:30:57 +00:00
8c288ad559 Basic add-to-private-book wizard, does nothing 2013-11-15 10:04:49 +00:00
df5d5ad38e Abstracted most of TunnelWizardActivity 2013-11-13 09:55:53 +00:00
8b1648c37b Only allow adding to private book 2013-11-13 07:57:58 +00:00
cea42e9ec4 Enable privatehosts.txt 2013-11-12 09:54:36 +00:00
64c44838a8 Hide list when loading, remove Toast notification from onLoadFinished()
Addressbook Loader works (from previous commit). Todo: put name count
in list header.
2013-11-12 05:35:08 +00:00
76f9259ee7 Filter addressbook in the Loader 2013-11-12 02:42:46 +00:00
d437f45132 Use TreeSet to sort Addressbook names 2013-11-12 02:33:19 +00:00
fc618ad9e5 Disabled country lookup, no GeoIP file 2013-11-12 02:20:39 +00:00
15275680e8 First pass at migrating Addressbook to use Loaders
Broken, addresses do not load into mAdapter after a tab change.
2013-11-12 01:10:29 +00:00
7848a81110 Added tabs to Addressbook 2013-11-12 01:01:34 +00:00
207f9837d0 Don't duplicate RouterContextProvider interface 2013-11-12 00:42:14 +00:00
cda09ea4f5 Suppress "unchecked" warnings 2013-11-11 21:41:26 +00:00
2a15994a76 Load real stats into NetDb ViewPager
getRouterContext() returns null in onActivityCreated(), there is a race
condition to fix somewhere, use refresh action to load.
2013-11-11 20:23:07 +00:00
e954953130 Reconnect to existing loader when screen rotated 2013-11-11 19:57:58 +00:00
8b51c26a6b Expanded NetDB detail pages to match /netdb 2013-11-11 10:02:52 +00:00
d40f806be6 Use Util.e() 2013-11-11 04:06:04 +00:00
bf5b29da76 Fixed NetDB detail filling 2013-11-11 04:03:08 +00:00
18c4276ba0 Fixed id 2013-11-11 04:01:39 +00:00
a4107e974a First pass at NetDB details, runs but View is not filled 2013-11-11 03:26:32 +00:00
8dd35f6c0f New translations 2013-11-11 03:23:40 +00:00
5a4be4ae86 Added missing file 2013-11-11 03:17:12 +00:00
9527725760 Process RI/LS in NetDbEntry, use DatabaseEntry when possible 2013-11-11 03:15:34 +00:00
b6a8fc02f5 Removed unused import 2013-11-11 01:25:21 +00:00
7794a7db5b Use manual action to refresh NetDB list instead of timer 2013-11-11 01:22:52 +00:00
c03debf332 Implemented Entry/Loader/Adapter for NetDbListFragment 2013-11-10 22:30:52 +00:00
fbc56d4eb9 Only use ViewPager for NetDb stats 2013-11-09 12:25:03 +00:00
2a050b3ca1 Updated AndroidManifest for renamed class 2013-11-09 12:20:28 +00:00
48c8d84d2f Fixed new version dialog text 2013-11-08 20:56:52 +00:00
b6d6258e95 Renamed Rate graph code 2013-11-08 19:41:17 +00:00
47215495ed add patch from ticket #1103, consistency fixes, remove some unneeded bashisms, set proper JAVA_HOME on OSX 2013-11-01 02:01:15 +00:00
9e45f1998b remove executable bit 2013-11-01 01:59:40 +00:00
a057e4a512 Show dialog if graphs are not ready 2013-09-28 13:36:31 +00:00
7a09670097 Removed unused import 2013-09-28 12:15:58 +00:00
c92d881a51 Dynamically fetch current list of graphs 2013-09-28 12:11:30 +00:00
8c2ba03880 Added settings for which graphs to plot
The onPause() method in SettingsFragment was removed as it is unnecessary. The
parent SettingsActivity onPause() is sufficient to save config changes.
2013-09-28 11:34:54 +00:00
9c9f871667 Fixed some warnings 2013-09-28 02:39:41 +00:00
7f9758197d Missing changes from previous commit 2013-09-28 02:18:11 +00:00
f6d1c093e4 Migrated SettingsActivity to preference headers 2013-09-28 02:17:35 +00:00
5e045bc23b Show current value in exploratory tunnel settings menu
TODO: Refresh summary when a setting is changed.
2013-09-26 21:23:30 +00:00
f667a81c6d Stop TunnelDetailFragment crashing if mTunnel is null
TODO: determine why mTunnel is not being set.
2013-09-25 03:52:27 +00:00
a13a405b49 Created white itoopie SVG, exported new notification icons 2013-09-25 03:38:36 +00:00
383ece497f Pulled in (and cleaned) SVG from i2p.itoopie, exported cleaner launcher icons 2013-09-25 02:16:30 +00:00
075c7c09d7 First draft of NetDB page: nav drawer w/ ViewPager, tabbed categories 2013-09-20 05:28:18 +00:00
7cec48e55f Combined error log and log views into a drop-down list 2013-09-08 06:48:58 +00:00
7ddd3c69c8 Save and restore selected rate graph 2013-09-07 13:49:16 +00:00
9d965a5504 Removed hard-coded title from graph (drop-down is more informative) 2013-09-07 13:44:55 +00:00
abd4a99654 Moved hard-coded list of graphs from nav drawer to actionbar drop-down 2013-09-07 13:42:27 +00:00
92c734624d Save and restore selected tunnel tab 2013-09-07 13:15:40 +00:00
81d0441d2b Implemented stat graphing
StatSummarizer and SummaryListener are based on the same-named classes from the
routerconsole. Data is stored in memory using an AndroidPlot SimpleXYSeries.
Only the last 30 points are currently stored.
2013-09-07 08:07:15 +00:00
9c7b2142cf AndroidPlot library version 0.6.0 from http://androidplot.com/download/
License: Apache 2.0
2013-09-07 08:05:33 +00:00
3da41888b7 Don't delete libs/ 2013-09-07 07:58:16 +00:00
bf47b901b7 Restored reload action (needed to update webpage if cached), fixed back nav 2013-09-06 00:53:03 +00:00
54a446ebdb Fixed Addressbook domain opening in left column of two-pane mode 2013-09-05 23:45:55 +00:00
0eb0c67616 Moved remaining links on main page to nav drawer
A WebActivity has been added back to encapsulate the web browser functionality,
allow the nav drawer items to be launched and enable other fixes.
2013-09-05 23:43:44 +00:00
409d823dec propagate from branch 'i2p.android.base' (head 673745663d325fbda1918746084110decd77634e)
to branch 'i2p.android.base.fragments' (head 9a651bea924de2d108bd743e4cb20958d281a4ff)
2013-09-05 23:02:46 +00:00
0208e58a3b Fixed previous commit
Refreshing is now disabled in PeersFragment and WebFragment, but both of them
will eventually be removed.
2013-09-05 12:25:32 +00:00
6d18e50a3a Removed start/stop and reload buttons from main menu 2013-09-05 11:48:08 +00:00
c748610280 Added PeersActivity; moved peers link to nav drawer for now 2013-09-05 11:43:41 +00:00
7768c624f9 New I2PFragmentBase so Fragments can access RouterContext, migration changes
All page logic is in the Fragments. Activities don't need the convenience
methods.
2013-09-05 06:07:34 +00:00
72ed6bd170 Migrated nav drawer code into I2PActivityBase 2013-09-05 03:54:09 +00:00
3d5b9938fd Migrated I2PFragmentBase back to I2PActivityBase
zzz and sponge pointed out that the router needs to be managed by whatever
Activity happens to be active (in case an automatic start is required). This
commit reverses the initial migration of I2PActivityBase to a Fragment (code is
broken now).
2013-09-05 03:39:15 +00:00
03b5927447 Updated refresh icon, removed old icons 2013-08-28 06:47:01 +00:00
b7a6b4acd5 Manifest change missing from previous commit 2013-08-28 06:02:51 +00:00
e27e1e55bd Implemented an Addressbook picker for Destination in tunnel wizard 2013-08-28 05:26:24 +00:00
0b0511dbce Added verification of Destination in tunnel wizard 2013-08-28 03:14:30 +00:00
02c370a04a Added input validation support to SingleTextFieldPage 2013-08-28 02:48:05 +00:00
4810c9e990 Implemented starting and stopping tunnels 2013-08-28 00:42:29 +00:00
cc801de79d More indent changes 2013-08-27 23:58:58 +00:00
93fd4f7e0c Fixed indent 2013-08-27 23:56:45 +00:00
abd9908a21 Moved tunnel list help handler to correct class, removed unused imports 2013-08-27 23:54:34 +00:00
52d278134c Added help link to tunnel list overflow menu 2013-08-27 23:51:57 +00:00
c60e4f6b3e Set up action buttons for starting and stopping tunnels 2013-08-27 23:47:18 +00:00
5b7452ff90 Fixed two-pane tunnel deletion 2013-08-27 11:12:47 +00:00
014fc9b79b Added edit and delete actions to tunnel detail fragment, implemented deletion 2013-08-27 10:11:01 +00:00
9ca0ce3192 Moved TunnelWizardModel parameter descriptions into resources 2013-08-27 04:01:20 +00:00
99d720c685 Implemented tunnel creation backend
The methods in TunnelConfig and TunnelUtil are taken almost verbatim from
IndexBean (modified to be static in TunnelUtil). The tunnel creation wizard
works now, and the new tunnel will start if auto start is enabled.
2013-08-27 03:53:07 +00:00
c46ba4f24b Moved tunnel wizard model key strings into resource, dismiss submit dialog 2013-08-27 03:36:27 +00:00
522178598b Pass tunnel wizard data back to TunnelListFragment 2013-08-26 11:50:12 +00:00
5b6658531e Readability, documentation of TunnelWizardModel 2013-08-26 08:59:36 +00:00
333455b738 Bugfixes 2013-08-26 08:21:05 +00:00
5ebdeedb2b Another required parameter 2013-08-26 06:44:13 +00:00
0af8d2145f Set more tunnel parameters as required 2013-08-26 04:26:55 +00:00
5ac6d51289 Added support to SingleTextFieldPage for setting a default string 2013-08-26 04:18:02 +00:00
238ab91092 Implement full tunnel wizard model 2013-08-26 02:32:44 +00:00
0af8ed90f7 Added NotEqual and EqualAny, removed show-on-null assumption 2013-08-26 02:31:08 +00:00
c761287a8a Added support for complex branching with Conditionals 2013-08-26 01:23:00 +00:00
a77674603a Restore checked status 2013-08-26 01:07:21 +00:00
9a2382d886 PagerAdapter -> MyPagerAdapter 2013-08-26 01:04:03 +00:00
0d58d81bce Added OnClickListener to tunnel wizard dialog, does nothing yet 2013-08-25 13:15:28 +00:00
1e4300cb83 Stretch or shrink the StepPagerStrip to fit the width by default
This is better for smaller devices. Larger devices will be customized later.
2013-08-25 13:14:20 +00:00
28b0950990 Added SimpleTextFieldPages for the main tunnel parameters needed 2013-08-25 13:13:10 +00:00
536102658a TextField* -> SingleTextField* for accuracy 2013-08-25 12:49:46 +00:00
6754f6b5b1 Tweaks to text field page, added a single boolean page for auto start option 2013-08-25 06:26:12 +00:00
06d1903184 Dummy tunnel creation wizard - most structure in place, does nothing 2013-08-25 01:39:37 +00:00
4d13e8adfd Added a TextFieldPage to WizardPager 2013-08-25 01:12:59 +00:00
145b249394 Added WizardPager code from https://github.com/romannurik/android-wizardpager 2013-08-25 01:06:06 +00:00
a00c08bb49 Moved I2PTunnel code into separate package 2013-08-24 06:26:51 +00:00
449627be3d Started expansion of I2PTunnel detail Fragment
Most of the getters in TunnelEntry are almost verbatim from
net.i2p.i2ptunnel.web.IndexBean and net.i2p.i2ptunnel.web.EditBean
2013-08-24 04:30:55 +00:00
27be4aacb2 Adjusted weighting of two-pane layout, set AddressbookActivity to use it 2013-08-22 22:12:41 +00:00
f4f849182d Added detail page for tunnels and a two-pane layout for large screens
There are a lot of changes here:
- The main_content FrameLayout is now main_fragment (more logical name).
- TunnelEntry now stores a Context instead of a TunnelEntryLoader (so
  it can be instantiated inside the detail Fragment).
- The activity_navdrawer layout is now an alias to a one-pane or two-pane
  layout depending on screen size.
- Subclasses of I2PActivityBase can now override canUseTwoPanes() to enable
  and use the two-pane layout (for devices that support it).
2013-08-22 14:52:00 +00:00
32d8a7112c Added missing I2PTunnel status stars 2013-08-22 14:37:45 +00:00
4dbfff292b Missing AndroidManifest update, prep code for help content 2013-08-22 01:48:50 +00:00
228d27d82b Moved licenses and release notes into menu of new HelpActivity 2013-08-22 01:46:42 +00:00
9d0858ad17 Override method for when nav drawer opens/closes to hide/show action bar items 2013-08-22 01:05:55 +00:00
480dacb7f2 Fixed warnings in SeekBarPreferences 2013-08-21 11:39:42 +00:00
84edc743f5 Fixed SeekBarPreferences to check resource strings 2013-08-21 11:34:31 +00:00
b7dfc45b1e Prep for I2PTunnel details fragment 2013-08-21 11:03:39 +00:00
bd0ebc8852 Removed Addressbook from menu (it is in the nav drawer) 2013-08-21 10:14:35 +00:00
8c0e2228a5 Communicate between Fragments via the container Activity, not directly 2013-08-21 09:57:14 +00:00
4dafc3e5af Import VersionDialog, not just the listener 2013-08-21 09:54:08 +00:00
291294435a Removed unused imports 2013-08-21 09:53:13 +00:00
99588c3cb1 Filled tunnel row layout with tunnel data 2013-08-19 00:15:00 +00:00
f7904e0c7e Added TunnelEntry to format TunnelController data, expanded tunnel row layout 2013-08-18 14:27:17 +00:00
ae2fa4dce7 Fill I2PTunnel list with a Loader
Until changes to the list of TunnelControllers can be monitored, the Loader
refreshes the view every 10 seconds.
2013-08-18 12:46:13 +00:00
bdbc777a52 Added TunnelControllerAdapter, filled tunnel lists with names
Router must be started and client tunnels built before loading the I2PTunnel
Activity, or it hangs when switching tabs.
2013-08-18 00:13:05 +00:00
320e8d5153 Added client and server tabs 2013-08-17 11:53:12 +00:00
c53b98d2b9 Changed LicenseFragment to a ListFragment 2013-08-17 11:21:54 +00:00
e1236d2824 Replaced list header with Toast in Addressbook 2013-08-17 09:37:29 +00:00
d7ac916eeb Changed AddressbookFragment to a ListFragment
Previous commit was not broken, a clean build fixed the problem.
2013-08-17 05:41:42 +00:00
5b9203f77d File framework for I2PTunnel interface
Broken: cannot find net.i2p.i2ptunnel.I2PTunnel
(but it can when launching the router)
2013-08-16 21:19:08 +00:00
9757d6e396 Added missing search icon for action bar 2013-08-16 21:17:21 +00:00
c2ff90af91 Fixed activity parents 2013-08-14 05:12:31 +00:00
c1e8719d0e Fixed bug in manifests 2013-08-14 04:46:04 +00:00
56198bf771 Moved logs and error logs into nav drawer 2013-08-14 04:40:19 +00:00
73286f43f6 Moved licenses button into nav drawer
Temporary; will eventually be in an about menu in settings
2013-08-14 04:22:58 +00:00
cd3157038c Fragmented LicenseActivity, turned TextResourceFragment into a DialogFragment 2013-08-14 04:11:37 +00:00
9359c7a726 Implemented Addressbook filtering via SearchView in action bar 2013-08-14 01:47:10 +00:00
058f41ec73 Set up SearchView in Addressbook action bar in preparation for searching
The navigation drawer has been (mostly) migrated to link to Activities instead
of Fragments. Only Activities can be searched via the inbuild Android search
function, which uses Intents.
2013-08-13 13:54:39 +00:00
2ef4d71cdb Renamed navigation drawer layout 2013-08-10 10:20:31 +00:00
c2bc999847 Turned navigation drawer code into base activity to subclass 2013-08-10 10:17:41 +00:00
d3f37a21a5 Show navigation drawer on first run 2013-08-10 09:16:19 +00:00
658d2a68e2 Removed unused imports 2013-08-10 05:43:41 +00:00
490148cb5b Migrated old Dialog to DialogFragment 2013-08-10 05:38:28 +00:00
430d56b681 Set choice mode for navigation drawer
This should have enabled the current navigation drawer entry to be highlighted
by the call to setItemChecked() in selectItem(), but it still doesn't work.
2013-08-10 03:43:34 +00:00
405bb3317e Show some menu items in action bar 2013-08-10 03:40:13 +00:00
bbb41c9c54 Use dp instead of px, added content description for logo 2013-08-10 03:29:58 +00:00
5c9c438e28 Moved Addressbook button to navigation drawer 2013-08-10 01:40:22 +00:00
27239cf09d Renamed menu1.xml 2013-08-10 01:26:12 +00:00
b853c3af39 Only call onBackPressed for I2PFragmentBase subclasses 2013-08-10 01:20:59 +00:00
17be8fb3f7 Load WebFragment properly from AddressbookFragment 2013-08-10 00:50:10 +00:00
bf5accb121 Converted AddressbookActivity to Fragment 2013-08-10 00:44:55 +00:00
4749e470b5 Fixed static reference 2013-08-09 23:35:32 +00:00
0553815777 Removed unused imports 2013-08-09 23:30:58 +00:00
57d81fb14d Migrated home buttons to use Fragment transactions 2013-08-09 23:30:37 +00:00
ee97af6e4f Fix labels in navigation drawer 2013-08-09 12:31:45 +00:00
6397a93cac Enable app icon for navigation drawer opening 2013-08-09 11:59:16 +00:00
03465185f9 Moved Fragments to net.i2p.android.router.fragment
Side-effect: some constant strings are now public
2013-08-09 11:52:33 +00:00
ddd9a195e6 Removed "I2P Home" from action bar (linked in navigation drawer) 2013-08-09 11:14:11 +00:00
664985461a Initial migration to navigation drawer and Fragment
- Migrated I2PActivityBase and subclasses to Fragment
- Added new MainActivity with navigation drawer to handle Fragments
- Added MainFragment to navigation drawer

Code compiles, installs and runs. Known bugs created:
- Some buttons now fail (can't use an Intent with a Fragment)
  -> Migrate usage of Intents to Fragment transactions
- New install/upgrade dialogs are commented out
  -> Migrate old Dialogs to DialogFragments
2013-08-09 11:11:37 +00:00
512 changed files with 134415 additions and 4212 deletions

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2p_router"/>
<classpathentry combineaccessrules="false" kind="src" path="/i2ptunnel"/>
<classpathentry combineaccessrules="false" kind="src" path="/BOB"/>
<classpathentry combineaccessrules="false" kind="src" path="/addressbook"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

View File

@ -24,9 +24,28 @@ _jsp\.java$
/classes/
# Android-specific ignores
^bin
^gen
^routerjars/bin
^routerjars/gen
AndroidManifest.xml
^routerjars/libs
local.properties
signing.properties
#IntelliJ IDEA
^.idea
.*.iml
.*.ipr
.*.iws
#Gradle
^.gradle
build
# I2P-specific ignores
^app/src/main/res/drawable/i2plogo.png
^app/src/main/res/raw/blocklist_txt
^app/src/main/res/raw/hosts_txt
^app/src/main/res/raw/license_
^app/src/main/res/raw/certificates_zip
^app/src/main/assets/themes/console/images
^app/src/main/assets/themes/console/light/console.css
^app/src/main/assets/themes/console/light/images/header.png
^scripts/build.number
^scripts/version.properties

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>I2P_Android</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -1,10 +1,17 @@
[main]
host = https://www.transifex.com
lang_map = ru_RU: ru, sv_SE: sv, tr_TR: tr, zh_CN: zh
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, zh_CN: zh
[I2P.android]
file_filter = res/values-<lang>/strings.xml
source_file = res/values/strings.xml
file_filter = app/src/main/res/values-<lang>/strings.xml
source_file = app/src/main/res/values/strings.xml
source_lang = en
type = ANDROID
minimum_perc = 50
[I2P.android_lib_client]
file_filter = client/src/main/res/values-<lang>/strings.xml
source_file = client/src/main/res/values/strings.xml
source_lang = en
type = ANDROID
minimum_perc = 50

View File

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.i2p.android.router"
android.versionCode="0"
android.versionName="0.0.0-0_b0-API8"
android:installLocation="auto"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-sdk android:minSdkVersion="8"
android:targetSdkVersion="18" />
<application android:label="@string/app_name"
android:theme="@style/Theme.AppCompat"
android:icon="@drawable/ic_launcher_itoopie" >
<service android:name=".service.RouterService"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher_itoopie" />
<provider android:name=".provider.CacheProvider"
android:authorities="net.i2p.android.router" />
<activity android:name=".activity.MainActivity"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher_itoopie"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.NewsActivity"
android:label="I2P News"
android:configChanges="orientation|keyboardHidden" >
</activity>
<activity android:name=".activity.TextResourceActivity"
android:label="I2P Information" >
</activity>
<activity android:name=".activity.LicenseActivity"
android:label="I2P License Information" >
</activity>
<activity android:name=".activity.WebActivity"
android:label="I2P Web Browser"
android:configChanges="orientation|keyboardHidden" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="*.i2p" android:scheme="http" />
</intent-filter>
</activity>
<activity android:name=".activity.SettingsActivity"
android:label="I2P Settings"
android:launchMode="singleTop" >
</activity>
<activity android:name=".activity.AddressbookSettingsActivity"
android:label="I2P Addressbook Settings"
android:launchMode="singleTop" >
</activity>
<activity android:name=".activity.AddressbookActivity"
android:label="Addressbook"
android:launchMode="singleTop" >
</activity>
<activity android:name=".activity.LogActivity"
android:label="I2P Logs" >
</activity>
<activity android:name=".activity.PeersActivity"
android:label="I2P Peers and Transport Status"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTop" >
</activity>
</application>
</manifest>

33
CHANGELOG Normal file
View File

@ -0,0 +1,33 @@
0.9.19
* Made internal state handling more stable
* Added graceful shutdown support
* Improved logging
* Bug fixes and translation updates
0.9.18 / 2015-03-04 / c2f4831a1617f4ce716a08640446fdd992c751ff
* I2P can start automatically when phone boots (configure in Setting)
* Updated browser configuration guides for Orfox and Firefox
* Tunnels for postman's mail server added to defaults (for new installs)
* Settings options for configuring UDP and TCP ports
* Bug fixes and translation updates
0.9.17.1 / 2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
* Fixed crashes in addressbook and netDb status page
* Fixed crash when opening an IRC client tunnel
* Updated translations
0.9.17 / 2014-12-01 / bcf947f433876f643e0f6dff81aac88848b797d3
* Migrated to the new Material design from Android Lollipop
* Added a browser configuration guide
* Improved the help screen
* Upgraded the I2P router to 0.9.17
* Various bug fixes and translation updates
0.9.15.1 / 2014-10-16 / 9cc4e80134cf9becb3838ed3cc78e0bed1363165
* Fixed a configuration bug
0.9.15 / 2014-10-16 / 9b51f78b791c28a580d57f1a5019c94493de6231
* Upgraded the I2P router to 0.9.15
* Added a help screen with some basic information
* Logs can now be copied to the clipboard
* Various user interface improvements and fixes

151
README.md Normal file
View File

@ -0,0 +1,151 @@
# I2P Android
## Build process
### Dependencies:
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
- Apache Ant 1.8.0 or higher
- I2P source
- Android SDK for API 21
- Android Build Tools 21.1.2
- Android Support Repository
- Gradle 2.2.1
### Gradle
The build system is based on Gradle. There are several methods for setting Gradle up:
* It can be downloaded from [the Gradle website](http://www.gradle.org/downloads).
* Most distributions will have Gradle packages. Be careful to check the
provided version; Debian and Ubuntu have old versions in their main
repositories. There is a [PPA](https://launchpad.net/~cwchien/+archive/gradle)
for Ubuntu with the latest version of Gradle.
* A Gradle wrapper is provided in the codebase. It takes all the same commands
as the regular `gradle` command. The first time that any command is run, it
will automatically download, cache and use the correct version of Gradle.
This is the simplest way to get started with the codebase. To use it, replace
`gradle` with `./gradlew` (or `./gradlew.bat` on Windows) in the commands
below.
Gradle will pull dependencies over the clearnet by default. To send all Gradle
connections from your user over Tor, create a `gradle.properties` file in
`~/.gradle/` containing:
```
systemProp.socksProxyHost=localhost
systemProp.socksProxyPort=9150
```
### Preparation
1. Download the Android SDK. The simplest method is to download [Android Studio](https://developer.android.com/sdk/installing/studio.html).
* If you are using an existing Android SDK, install the Android Support
Repository via the SDK Manager.
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
```
i2psrc=/path/to/i2p.i2p
```
### Building from the command line
1. Create a `local.properties` file in `i2p.android.base` containing:
```
sdk.dir=/path/to/android-studio/sdk
```
2. `gradle assembleDebug`
3. The APK will be placed in `i2p.android.base/app/build/outputs/apk`.
### Building with Android Studio
1. Import `i2p.android.base` into Android Studio. (This creates the `local.properties` file automatically).
2. Build and run the app (`Shift+F10`).
### Signing release builds
1. Create a `signing.properties` file in `i2p.android.base` containing:
```
STORE_FILE=/path/to/android.keystore
STORE_PASSWORD=store.password
KEY_ALIAS=key.alias
KEY_PASSWORD=key.password
```
2. `gradle assembleRelease`
## Client library
### "Uploading" to the local Maven repository (to use a local build of the library in a project)
1. `gradle :client:installArchives`
2. Add the local Maven repository to your project. For Gradle projects, add the following above any existing repositories (so it is checked first):
```
repositories {
mavenLocal()
}
```
### Uploading to Maven Central via Sonatype OSSRH
1. Add the following lines to your `~/.gradle/gradle.properties` (filling in the blanks):
```
signing.keyId=
signing.password=
signing.secretKeyRingFile=/path/to/secring.gpg
ossrhUsername=
ossrhPassword=
```
2. `gradle :client:uploadArchives`
### Commands from the old build instructions that might be useful
```
# Create the android 4.4 (API 19) virtual device
# (don't make a custom hardware profile)
../android-sdk-linux/tools/android create avd --name i2p --target android-19
# then run the emulator:
# This may take a LONG time the first time (half an hour or more)...
# Run the debugger to ensure it is making progress
# -no-boot-anim for faster boot
# -dns-server 8.8.8.8 if the router can't reseed
# ../android-sdk-linux/tools/emulator -avd i2p -no-boot-anim -dns-server 8.8.8.8 &
../android-sdk-linux/tools/emulator -avd i2p &
# or to talk to a real device in debug mode:
# You have to do this if you get a permission error -
# Stop ddms, unplug the device, do the following,
# then plug in the device, then start ddms
adb kill-server
sudo adb start-server
adb devices
# Anyway, with I2P installed, click on the I2P icon on your device and enjoy!
#other helpful commands
../android-sdk-linux/platform-tools/adb shell
../android-sdk-linux/platform-tools/adb pull /some/file/on/emulator some-local-dir/
# copy the Dev Tools app from the emulator to your device
adb -e pull /system/app/Development.apk ./Development.apk
adb -d install Development.apk
# reinstall an existing apk onto the emulator
adb -e install -r bin/I2PAndroid-debug.apk
```

View File

@ -1,113 +0,0 @@
These instructions are for a recent Android SDK (Rev 20 or better) on Linux.
Windows building is not currently supported.
These instructions were last updated for SDK Tools Version 20 with
SDK Platform-tools Version 12 from updates.
The i2p source must be installed in ../i2p.i2p,
or else add i2psrc=/path/to/source in the local.properties file.
=====================
Dependencies:
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
- Apache Ant 1.8.0 or higher
- I2P source in ../i2p.i2p
- Android SDK (tested with Rev 20 and platform-tools version 12)
=====================
Instructions:
# Download the SDK from http://developer.android.com/sdk/index.html
# Unzip the android SDK in ../
# So then the android tools will be in ../android-sdk-linux/tools/
#
# Run the GUI updater, which you must do to get an SDK Platform:
../android-sdk-linux/tools/android &
# now go to the available packages tab, check the box and click refresh,
# and download an SDK Platform
# Since I2P is targeted at 4.3 (API 18)
# download at least that one. Otherwise you must change the
# target in project.properties from android-18 to andriod-x
# where x is the API version.
# I2P is configured to run on 2.2 (API 8) or higher using the
# Android Support Library, so download that as well
# (it's under "Extras").
# To run the debugger (ddms) you also need to download the
# "Android SDK Platform-Tools" package from the GUI updater.
# create a file local.properties with the following line (without the leading # of course),
# do NOT use a relative path
# sdk.dir=/path/to/your/android-sdk-linux
# Copy this file to the routerjars/ directory, it is needed in both places.
# If your SDK is not in ../android-sdk-linux/ then you must
# override the location of the Android Support Library. Add
# the following line to local.properties
# do NOT use an absolute path
# android.library.reference.2=path/to/your/android-sdk-linux/extras/android/support/v7/appcompat
# Don't add it to the local.properties in the routerjars/ directory.
# DO NOT create a new project or anything. It's all set up right here for you.
# Create the android 4.3 (API 18) virtual device
# (don't make a custom hardware profile)
../android-sdk-linux/tools/android create avd --name i2p --target 18
# then run the emulator:
# This may take a LONG time the first time (half an hour or more)...
# Run the debugger to ensure it is making progress
# -no-boot-anim for faster boot
# -dns-server 8.8.8.8 if the router can't reseed
# ../android-sdk-linux/tools/emulator -avd i2p -no-boot-anim -dns-server 8.8.8.8 &
../android-sdk-linux/tools/emulator -avd i2p &
# or to talk to a real device in debug mode:
# You have to do this if you get a permission error -
# Stop ddms, unplug the device, do the following,
# then plug in the device, then start ddms
adb kill-server
sudo adb start-server
adb devices
# then wait a couple minutes until the emulator or device is up
# compile and install for a release
ant release
ant installr
# or compile and install for a debug version
ant debug
ant installd
# then run the debugger
../android-sdk-linux/tools/ddms &
# to rebuild and reinstall to emulator or device:
ant clean
# then do which ever from the above compile and install choices.
# to uninstall
ant uninstall
# or use your device's menu.
# Other ant tagets are available, just type
ant
# Anyway, with I2P installed, click on the I2P icon on your device and enjoy!
#other helpful commands
../android-sdk-linux/platform-tools/adb shell
../android-sdk-linux/platform-tools/adb pull /some/file/on/emulator some-local-dir/
# copy the Dev Tools app from the emulator to your device
adb -e pull /system/app/Development.apk ./Development.apk
adb -d install Development.apk
# reinstall an existing apk onto the emulator
adb -e install -r bin/I2PAndroid-debug.apk

96
TODO Normal file
View File

@ -0,0 +1,96 @@
# Fixes
- Better twopane column widths
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
- Create tunnel wizard
<zzz> hmm would be nice if they could be shared-client or have an option
<zzz> was setting up email tunnels
- Browser
<zzzccc> Bug report: i2p browser treats 302 as an error
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page
# Short-term
- Disable uPnP when on cell networks
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
- I2PTunnel
- Show all messages somewhere
- Improve detail page, expose advanced settings
- Add edit page
- Progress feedback for addressbook subscriptions reload
- Display release notes directly on new router version
- Fill out help pages
- Rewrite release notes to be release-specific
- Fix release notes UI, either make back button use clear or add buttons
- NetDB tablet view fixes
- Refresh detail fragment when changing tab
- Move list to correct item when changing tab
- Create nav history when viewing RI from LS
- Include GeoIP db for country info
- Maybe change router-off mechanic for various pages? Enable as they become available?
# Medium-term
- Network profiles
- User selects profile in settings
- Change network participation etc. based on profile
- Also look at connection type: Connectivity.isConnectionFast()
- Expose log level overrides
- Improve graphs
- Show time on bottom axis
- Show fixed x range, not only available data
- Think about pan/zoom
- How to persist data across restarts?
# Silent Store approval checks to confirm/implement
- Known Vulnerabilities
- Apps will be tested to ensure that they are not susceptible to known
publicly disclosed vulnerabilities. For example:
- Heartbleed
- Poodle
- MasterKey
- Common Path Traversal attacks
- Common SQL Injection attacks
- Network Security Protocols
- All Apps that require transmission of data from the App to a system that
does not exist on the device must use, at a minimum, TLS1.1 standards.
However, Blackphone would prefer the usage of TLS1.2.
- Apps must not use algorithms for cryptographic purposes that are considered
obsolete or outdated i.e. MD5, SHA1, RC4, DES, or any encryption algorithm
that is weaker than AES128.
- Transport Layer Protection
- All network communication should be encrypted
- Not vulnerable to SSl Strip
- Data Leakage
- No storage of sensitive data outside of application sandbox
- Files should not be created with MODE_WORLD_READABLE or MODE_WORLD_WRITABLE
- Copy & Paste will be evaluated on a case by case basis
- App logs should not contain sensitive information
- Authentication and Authorization
- Validate that authentication credentials are not stored on the device
- Must use an approved password-based key derivation function ie. PBKDF2, scrypt
- Data-at-rest Encryption
- Must use at a minimum AES128 with modes CCM or GCM
- Should not store the encryption key on the file system
- Permission Checks
- The App must function with all permissions disabled
- Apps must not hard crash if a permission is disabled
- Apps should ask users to enable permissions that are disabled if needed to
function properly and explain why the permission is necessary
- Privacy Policy
- Apps must have a privacy policy that details how customer data is used,
stored, shared, etc...
- Apps must be configured with the customer opted out by default
- App logs should not contain PII
- Error Handling
- Apps should follow best-practices for error handling and logging
# Long-term
- Remote router support
- Implement a "router wrapper" that can represent a local or remote router
- Implement/use client APIs to talk to remote router
- I2CP
- I2PControl

View File

@ -1,4 +0,0 @@
application-package=net.i2p.router
key.store=${user.home}/.android/${application-package}.keystore
key.alias=${application-package}
key.store.password=android

206
app/build.gradle Normal file
View File

@ -0,0 +1,206 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
android {
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
defaultConfig {
versionCode 4745227
versionName '0.9.19'
minSdkVersion 9
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
// For Espresso
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'LICENSE.txt'
}
productFlavors {
free {
applicationId 'net.i2p.android'
}
donate {
applicationId 'net.i2p.android.donate'
}
legacy {
applicationId 'net.i2p.android.router'
}
}
}
dependencies {
// Local dependencies
compile project(':routerjars')
compile project(':client')
// Android Support Repository dependencies
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
// Remote dependencies
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0'
compile files('libs/androidplot-core-0.6.1.jar')
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4'
}
// Testing-only dependencies
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
}
dependencyVerification {
verify = [
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
]
}
project.ext.i2pbase = '../i2p.i2p'
def Properties props = new Properties()
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null &&
props.containsKey('i2psrc')) {
i2pbase = props['i2psrc']
} else {
println 'local.properties found but some entries are missing'
}
} else {
println 'local.properties not found'
}
task certificatesZip(type: Zip) {
archiveName = 'certificates_zip'
from files('' + i2pbase + '/installer/resources/certificates')
}
task copyI2PResources(type: Copy) {
// Force this to always run: Copy only detects source changes, not if missing in destination
outputs.upToDateWhen { false }
into 'src/main/res'
into('drawable') {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
}
into('raw') {
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
from('../LICENSE.txt') { rename { 'license_app_txt' } }
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
from(i2pbase + '/licenses') {
include { elem ->
elem.name in [
'LICENSE-ElGamalDSA.txt',
'LICENSE-SHA256.txt',
'LICENSE-BSD.txt',
'LICENSE-SNTP.txt',
'LICENSE-LGPLv2.1.txt',
'LICENSE-InstallCert.txt',
'LICENSE-BlockFile.txt',
'LICENSE-GPLv2.txt',
'LICENSE-GPLv3.txt',
'LICENSE-LGPLv3.txt',
'LICENSE-FatCowIcons.txt',
'LICENSE-Addressbook.txt',
]
}
rename { String name ->
String part = name.substring(8, name.lastIndexOf('.txt'))
String.format('license_%s_txt',
part.toLowerCase(Locale.US).replace('.', '_'))
}
}
from certificatesZip
}
}
// For peers WebView
task copyI2PAssets(type: Copy) {
// Force this to always run: Copy only detects source changes, not if missing in destination
outputs.upToDateWhen { false }
into 'src/main/assets/themes/console'
into('images') {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
}
into('light') {
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
}
into('light/images') {
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
}
}
preBuild.dependsOn copyI2PResources
preBuild.dependsOn copyI2PAssets
task cleanI2PResources(type: Delete) {
delete file('src/main/res/drawable/i2plogo.png')
delete fileTree('src/main/res/raw') {
include 'blocklist_txt'
include 'hosts_txt'
include 'license_*'
include 'certificates_zip'
}
}
task cleanI2PAssets(type: Delete) {
delete fileTree('src/main/assets/themes/console/images')
delete file('src/main/assets/themes/console/light/console.css')
delete file('src/main/assets/themes/console/light/images/header.png')
}
clean.dependsOn cleanI2PResources
clean.dependsOn cleanI2PAssets
props = new Properties()
propFile = new File(project.rootDir, 'signing.properties')
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null &&
props.containsKey('STORE_FILE') &&
props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') &&
props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
} else {
println 'signing.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'signing.properties not found'
android.buildTypes.release.signingConfig = null
}

Binary file not shown.

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">I2P DEBUG</string>
</resources>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.i2p.android.router"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name"
android:theme="@style/Theme.I2P">
<service
android:name=".service.RouterService"
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name">
<intent-filter>
<action android:name="net.i2p.android.router.service.IRouterState" />
</intent-filter>
</service>
<provider
android:name=".provider.CacheProvider"
android:authorities="${applicationId}.provider" />
<receiver android:name=".receiver.OnBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="net.i2p.android.router.START_I2P" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".NewsActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/label_news"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.help.HelpActivity"
android:label="@string/menu_help"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.help.BrowserConfigActivity"
android:label="@string/label_browser_configuration"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".LicenseActivity"
android:label="@string/label_licenses"
android:parentActivityName="net.i2p.android.help.HelpActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.help.HelpActivity" />
</activity>
<activity
android:name=".web.WebActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P Web Browser">
<!-- Disabled, this browser is not very secure
Temporarily enabled until an alternative browser is ready -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="*.i2p"
android:scheme="http" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/menu_settings"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookSettingsActivity"
android:label="Addressbook Settings"
android:launchMode="singleTop"
android:parentActivityName=".addressbook.AddressbookActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookActivity"
android:label="@string/label_addressbook"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
</activity>
<activity
android:name=".addressbook.AddressbookAddWizardActivity"
android:label="Add new Destination"
android:parentActivityName=".addressbook.AddressbookActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
android:label="@string/label_i2ptunnel"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
android:label="I2PTunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.i2ptunnel.TunnelListActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.EditTunnelActivity"
android:label="@string/edit_tunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelDetailActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.i2ptunnel.TunnelDetailActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
android:label="Tunnel Creation Wizard"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
</activity>
<activity
android:name=".log.LogActivity"
android:label="I2P Logs"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".log.LogDetailActivity"
android:label="Log Entry"
android:parentActivityName=".log.LogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.log.LogActivity" />
</activity>
<activity
android:name=".stats.RateGraphActivity"
android:label="Rate Graph"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".stats.PeersActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P Peers and Transport Status"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".netdb.NetDbActivity"
android:label="NetDB"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".netdb.NetDbDetailActivity"
android:label="NetDB Detail"
android:parentActivityName=".netdb.NetDbActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.netdb.NetDbActivity" />
</activity>
</application>
</manifest>

View File

@ -49,8 +49,14 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mDialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
mSuffix = attrs.getAttributeValue(androidns, "text");
int dialogMessageR = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
mDialogMessage = (dialogMessageR == 0)
? attrs.getAttributeValue(androidns, "dialogMessage")
: context.getResources().getString(dialogMessageR);
int textR = attrs.getAttributeResourceValue(androidns, "text", 0);
mSuffix = (textR == 0)
? attrs.getAttributeValue(androidns, "text")
: context.getResources().getString(textR);
mDefault = attrs.getAttributeValue(androidns, "defaultValue");
mMax = Integer.parseInt(attrs.getAttributeValue(androidns, "max"));
if (attrs.getAttributeValue(androidns, "direction") != null) {
@ -84,7 +90,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
@ -93,7 +99,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
// Move the bar away from the changing text, so you can see it, and
// move it away from the edges to improve usability for the end-ranges.
mSeekBar.setPadding(6, 30, 6, 6);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist()) {
mValue = Integer.parseInt(getPersistedString(mDefault));
@ -126,7 +132,7 @@ public class SeekBarPreference extends DialogPreference implements SeekBar.OnSee
if (shouldPersist()) {
persistString(t);
}
callChangeListener(new Integer(value));
callChangeListener(Integer.valueOf(value));
}
public void onStartTrackingTouch(SeekBar seek) {

View File

@ -1,6 +1,9 @@
package net.i2p.android.apps;
import java.io.File;
import net.i2p.android.router.NewsActivity;
import net.i2p.android.router.util.Notifications;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.util.RFC822Date;
@ -15,6 +18,7 @@ import net.i2p.util.Translate;
*/
public class NewsFetcher implements Runnable, EepGet.StatusListener {
private final RouterContext _context;
private final Notifications _notif;
private final Log _log;
private long _lastFetch;
private long _lastUpdated;
@ -23,31 +27,43 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private File _newsFile;
private File _tempFile;
private static NewsFetcher _instance;
private volatile boolean _isRunning = true;
private Thread _thread;
public static /*final */ NewsFetcher getInstance() {
return _instance;
}
public static /* final */ synchronized NewsFetcher getInstance(RouterContext ctx) {
public static /* final */ synchronized NewsFetcher getInstance(
RouterContext ctx, Notifications notif) {
if (_instance != null)
return _instance;
_instance = new NewsFetcher(ctx);
_instance = new NewsFetcher(ctx, notif);
return _instance;
}
private static final String NEWS_DIR = "docs";
private static final String NEWS_FILE = "news.xml";
private static final String TEMP_NEWS_FILE = "news.xml.temp";
/** @since 0.7.14 not configurable */
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
/**
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
* We may be able to change it to psi.i2p in a future release after
* the hostname propagates.
*
* @since 0.7.14 not configurable
*/
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml";
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
private static final String PROP_NEWS_URL = "router.newsURL";
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
private NewsFetcher(RouterContext ctx) {
private NewsFetcher(RouterContext ctx, Notifications notif) {
_context = ctx;
_notif = notif;
_context.addShutdownTask(new Shutdown());
_log = ctx.logManager().getLog(NewsFetcher.class);
try {
String last = ctx.getProperty(PROP_LAST_CHECKED);
@ -97,14 +113,14 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private static final long INITIAL_DELAY = 5*60*1000;
private static final long RUN_DELAY = 30*60*1000;
@SuppressWarnings("SleepWhileInLoop")
public void run() {
_thread = Thread.currentThread();
try {
Thread.sleep(INITIAL_DELAY);
} catch (InterruptedException ie) {
return;
}
while (true) {
while (_isRunning && _context.router().isAlive()) {
if (shouldFetchNews()) {
fetchNews();
}
@ -193,7 +209,10 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
if (copied) {
_lastUpdated = now;
_tempFile.delete();
// notify somebody?
// Notify user
_notif.notify("News Updated", "Touch to view latest I2P news",
NewsActivity.class);
} else {
if (_log.shouldLog(Log.ERROR))
_log.error("Failed to copy the news file!");
@ -214,4 +233,12 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
private class Shutdown implements Runnable {
public void run() {
_isRunning = false;
if (_thread != null)
_thread.interrupt();
}
}
}

View File

@ -0,0 +1,83 @@
package net.i2p.android.help;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
public class Browser implements Comparable<Browser> {
public final String packageName;
public final CharSequence label;
public final Drawable icon;
public final boolean isInstalled;
public final boolean isKnown;
public final boolean isSupported;
public final boolean isRecommended;
/**
* A browser that we don't know about.
*
* @param pm the PackageManager used to find the browser
* @param browser the browser
*/
public Browser(PackageManager pm, ResolveInfo browser) {
this(
browser.activityInfo.packageName,
browser.loadLabel(pm),
browser.loadIcon(pm),
true, false, false, false
);
}
/**
* A browser that we know about.
*
* @param pm the PackageManager used to find the browser
* @param browser the browser
* @param supported can this browser be used with I2P?
*/
public Browser(PackageManager pm, ResolveInfo browser, boolean supported, boolean recommended) {
this(
browser.activityInfo.packageName,
browser.loadLabel(pm),
browser.loadIcon(pm),
true, true, supported, recommended
);
}
public Browser(String pn, CharSequence l, Drawable ic, boolean i, boolean k, boolean s, boolean r) {
packageName = pn;
label = l;
icon = ic;
isInstalled = i;
isKnown = k;
isSupported = s;
isRecommended = r;
}
@Override
public int compareTo(@NonNull Browser browser) {
// Sort order: supported -> unknown -> unsupported
int a = getOrder(this);
int b = getOrder(browser);
if (a < b)
return -1;
else if (a > b)
return 1;
return label.toString().compareTo(browser.label.toString());
}
private static int getOrder(Browser browser) {
if (browser.isKnown) {
if (browser.isRecommended)
return 0;
else if (browser.isSupported)
return 1;
else
return 3;
} else
return 2;
}
}

View File

@ -0,0 +1,117 @@
package net.i2p.android.help;
import android.content.Context;
import android.content.Intent;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import net.i2p.android.router.R;
public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHolder> {
private Context mCtx;
private Browser[] mBrowsers;
private OnBrowserSelectedListener mListener;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView mIcon;
public TextView mLabel;
public ImageView mStatus;
public ViewHolder(View v) {
super(v);
mIcon = (ImageView) v.findViewById(R.id.browser_icon);
mLabel = (TextView) v.findViewById(R.id.browser_label);
mStatus = (ImageView) v.findViewById(R.id.browser_status_icon);
}
}
public static interface OnBrowserSelectedListener {
public void onBrowserSelected(Browser browser);
}
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {
mCtx = ctx;
mListener = listener;
}
public void setBrowsers(Browser[] browsers) {
mBrowsers = browsers;
notifyDataSetChanged();
}
public void clear() {
mBrowsers = null;
notifyDataSetChanged();
}
// Create new views (invoked by the layout manager)
@Override
public BrowserAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.listitem_browser, parent, false);
return new ViewHolder(v);
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Browser browser = mBrowsers[position];
holder.mIcon.setImageDrawable(browser.icon);
holder.mLabel.setText(browser.label);
if (browser.isKnown) {
if (browser.isRecommended && browser.isInstalled) {
holder.mStatus.setImageDrawable(
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
holder.mStatus.setVisibility(View.VISIBLE);
} else if (browser.isSupported && !browser.isInstalled) {
holder.mStatus.setImageDrawable(
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
holder.mStatus.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String uriMarket = "market://search?q=pname:" + browser.packageName;
Uri uri = Uri.parse(uriMarket);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
mCtx.startActivity(intent);
}
});
holder.mStatus.setVisibility(View.VISIBLE);
} else if (!browser.isSupported) {
// Make the icon gray-scale to show it is unsupported
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
holder.mIcon.setColorFilter(filter);
holder.mLabel.setTextColor(
mCtx.getResources().getColor(R.color.primary_text_disabled_material_dark));
}
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mListener.onBrowserSelected(browser);
}
});
}
// Return the size of the dataset (invoked by the layout manager)
@Override
public int getItemCount() {
if (mBrowsers != null)
return mBrowsers.length;
return 0;
}
}

View File

@ -0,0 +1,94 @@
package net.i2p.android.help;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import net.i2p.android.router.R;
import java.lang.reflect.Field;
public class BrowserConfigActivity extends ActionBarActivity implements
BrowserAdapter.OnBrowserSelectedListener {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, new BrowserListFragment())
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// BrowserAdapter.OnBrowserSelected
@Override
public void onBrowserSelected(Browser browser) {
int file;
if (browser.isKnown) {
if (browser.isSupported) {
// Check for embedded browser
if (browser.packageName.startsWith("net.i2p.android"))
file = R.raw.help_embedded_browser;
else {
// Load the configuration guide for this browser
try {
String name = "help_" + browser.packageName.replace('.', '_');
Class res = R.raw.class;
Field field = res.getField(name);
file = field.getInt(null);
} catch (Exception e) {
file = R.raw.help_unknown_browser;
}
}
} else
file = R.raw.help_unsupported_browser;
} else
file = R.raw.help_unknown_browser;
HelpHtmlFragment configFrag = HelpHtmlFragment.newInstance(file);
if (mTwoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, configFrag)
.commit();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, configFrag)
.addToBackStack("config" + browser.packageName)
.commit();
}
}
}

View File

@ -0,0 +1,187 @@
package net.i2p.android.help;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import net.i2p.android.router.R;
import net.i2p.android.router.util.BetterAsyncTaskLoader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BrowserListFragment extends Fragment implements
LoaderManager.LoaderCallbacks<List<Browser>> {
private static final int BROWSER_LOADER_ID = 1;
private BrowserAdapter.OnBrowserSelectedListener mCallback;
private BrowserAdapter mAdapter;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (BrowserAdapter.OnBrowserSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnBrowserSelectedListener");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
// use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new BrowserAdapter(getActivity(), mCallback);
mRecyclerView.setAdapter(mAdapter);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(BROWSER_LOADER_ID, null, this);
}
// LoaderManager.LoaderCallbacks<List<Browser>>
@Override
public Loader<List<Browser>> onCreateLoader(int id, Bundle args) {
return new BrowserLoader(getActivity());
}
public static class BrowserLoader extends BetterAsyncTaskLoader<List<Browser>> {
private List<String> recommended;
private List<String> recommendedLabels;
private List<String> supported;
private List<String> supportedLabels;
private List<String> unsupported;
public BrowserLoader(Context context) {
super(context);
recommended = Arrays.asList(
getContext().getResources().getStringArray(R.array.recommended_browsers));
recommendedLabels = Arrays.asList(
getContext().getResources().getStringArray(R.array.recommended_browser_labels));
supported = Arrays.asList(
getContext().getResources().getStringArray(R.array.supported_browsers));
supportedLabels = Arrays.asList(
getContext().getResources().getStringArray(R.array.supported_browser_labels));
unsupported = Arrays.asList(
context.getResources().getStringArray(R.array.unsupported_browsers));
}
@Override
public List<Browser> loadInBackground() {
List<Browser> browsers = new ArrayList<>();
Map<String, String> recommendedMap = new HashMap<>();
for (int i = 0; i < recommended.size(); i++) {
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
}
Map<String, String> supportedMap = new HashMap<>();
for (int i = 0; i < supported.size(); i++) {
supportedMap.put(supported.get(i), supportedLabels.get(i));
}
// Find all installed browsers that listen for ".i2p"
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://stats.i2p"));
final PackageManager pm = getContext().getPackageManager();
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
for (ResolveInfo browser : installedBrowsers) {
if (recommended.contains(browser.activityInfo.packageName)) {
browsers.add(new Browser(pm, browser, true, true));
recommendedMap.remove(browser.activityInfo.packageName);
} else if (supported.contains(browser.activityInfo.packageName) ||
browser.activityInfo.packageName.startsWith("net.i2p.android")) {
browsers.add(new Browser(pm, browser, true, false));
supportedMap.remove(browser.activityInfo.packageName);
} else if (unsupported.contains(browser.activityInfo.packageName))
browsers.add(new Browser(pm, browser, false, false));
else
browsers.add(new Browser(pm, browser));
}
// Now add the remaining recommended and supported browsers
for (Map.Entry<String, String> browser : recommendedMap.entrySet()) {
browsers.add(new Browser(browser.getKey(), browser.getValue(),
getDrawableForPackage(browser.getKey()),
false, true, true, true));
}
for (Map.Entry<String, String> browser : supportedMap.entrySet()) {
browsers.add(new Browser(browser.getKey(), browser.getValue(),
getDrawableForPackage(browser.getKey()),
false, true, true, false));
}
Collections.sort(browsers);
return browsers;
}
private Drawable getDrawableForPackage(String packageName) {
try {
String name = "icon_" + packageName.replace('.', '_');
Class res = R.drawable.class;
Field field = res.getField(name);
int drawable = field.getInt(null);
return getContext().getResources().getDrawable(drawable);
} catch (Exception e) {
return null;
}
}
@Override
protected void onStartMonitoring() {
}
@Override
protected void onStopMonitoring() {
}
@Override
protected void releaseResources(List<Browser> data) {
}
}
@Override
public void onLoadFinished(Loader<List<Browser>> listLoader, List<Browser> browsers) {
if (listLoader.getId() == BROWSER_LOADER_ID)
mAdapter.setBrowsers(browsers.toArray(new Browser[browsers.size()]));
}
@Override
public void onLoaderReset(Loader<List<Browser>> listLoader) {
if (listLoader.getId() == BROWSER_LOADER_ID)
mAdapter.clear();
}
}

View File

@ -0,0 +1,155 @@
package net.i2p.android.help;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
public class HelpActivity extends ActionBarActivity implements
HelpListFragment.OnEntrySelectedListener {
public static final String CATEGORY = "help_category";
public static final int CAT_MAIN = 0;
public static final int CAT_CONFIGURE_BROWSER = 1;
public static final int CAT_ADDRESSBOOK = 2;
public static final int CAT_I2PTUNNEL = 3;
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private int mCategory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, new HelpListFragment())
.commit();
}
mCategory = getIntent().getIntExtra(CATEGORY, -1);
if (mCategory >= 0) {
showCategory(mCategory);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (mCategory >= 0) {
onBackPressed();
} else {
Intent upIntent = NavUtils.getParentActivityIntent(this);
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is NOT part of this app's task, so create a new task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
// Add all of this activity's parents to the back stack
.addNextIntentWithParentStack(upIntent)
// Navigate up to the closest parent
.startActivities();
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
}
return true;
case R.id.menu_help_licenses:
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
startActivity(lic);
return true;
case R.id.menu_help_release_notes:
TextResourceDialog dialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
dialog.setArguments(args);
dialog.show(getSupportFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (mCategory >= 0)
mCategory = -1;
}
// HelpListFragment.OnEntrySelectedListener
@Override
public void onEntrySelected(int entry) {
if (entry == CAT_CONFIGURE_BROWSER) {
Intent i = new Intent(this, BrowserConfigActivity.class);
startActivity(i);
} else {
mCategory = entry;
showCategory(entry);
}
}
private void showCategory(int category) {
int file;
switch (category) {
case CAT_ADDRESSBOOK:
file = R.raw.help_addressbook;
break;
case CAT_I2PTUNNEL:
file = R.raw.help_i2ptunnel;
break;
case CAT_MAIN:
default:
file = R.raw.help_main;
break;
}
HelpHtmlFragment f = HelpHtmlFragment.newInstance(file);
if (mTwoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, f).commit();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f)
.addToBackStack("help" + category)
.commit();
}
}
}

View File

@ -0,0 +1,36 @@
package net.i2p.android.help;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import net.i2p.android.router.R;
import org.sufficientlysecure.htmltextview.HtmlTextView;
public class HelpHtmlFragment extends Fragment {
public static final String ARG_HTML_FILE = "htmlFile";
static HelpHtmlFragment newInstance(int htmlFile) {
HelpHtmlFragment f = new HelpHtmlFragment();
Bundle args = new Bundle();
args.putInt(ARG_HTML_FILE, htmlFile);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ScrollView scroller = new ScrollView(getActivity());
HtmlTextView text = new HtmlTextView(getActivity());
scroller.addView(text);
int padH = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
int padV = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);
text.setPadding(padH, padV, padH, padV);
text.setHtmlFromRawResource(getActivity(), getArguments().getInt(ARG_HTML_FILE), true);
return scroller;
}
}

View File

@ -0,0 +1,47 @@
package net.i2p.android.help;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import net.i2p.android.router.R;
public class HelpListFragment extends ListFragment {
OnEntrySelectedListener mEntrySelectedCallback;
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(int entry);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEntrySelectedListener");
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(ArrayAdapter.createFromResource(getActivity(),
R.array.help_categories, R.layout.listitem_text));
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
super.onListItemClick(parent, view, pos, id);
mEntrySelectedCallback.onEntrySelected(pos);
}
}

View File

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

View File

@ -0,0 +1,272 @@
package net.i2p.android.i2ptunnel;
import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.support.v4.preference.PreferenceFragment;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelLogic;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
public class EditTunnelFragment extends PreferenceFragment {
private static final String ARG_TUNNEL_ID = "tunnelId";
private TunnelControllerGroup mGroup;
private int mTunnelId;
public static EditTunnelFragment newInstance(int tunnelId) {
EditTunnelFragment f = new EditTunnelFragment();
Bundle args = new Bundle();
args.putInt(ARG_TUNNEL_ID, tunnelId);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
// TODO Show error
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
loadPreferences();
}
}
@Override
public void onPause() {
super.onPause();
// Pre-Honeycomb: onPause() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
@Override
public void onStop() {
super.onStop();
// Honeycomb and above: onStop() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
private void saveTunnel() {
if (mGroup != null) {
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
}
}
private void loadPreferences() {
String type = TunnelUtil.getController(mGroup, mTunnelId).getType();
new TunnelPreferences(type).runLogic();
}
class TunnelPreferences extends TunnelLogic {
PreferenceScreen ps;
PreferenceCategory generalCategory;
PreferenceCategory portCategory;
PreferenceScreen advanced;
PreferenceCategory tunParamCategory;
public TunnelPreferences(String type) {
super(type);
}
@Override
protected void general() {
addPreferencesFromResource(R.xml.tunnel_gen);
ps = getPreferenceScreen();
generalCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_GENERAL));
portCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_PORT));
}
@Override
protected void generalClient() {
addPreferencesFromResource(R.xml.tunnel_gen_client, generalCategory);
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr) {
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_SHARED_CLIENT)));
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_PORT)));
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
}
@Override
protected void generalClientPort() {
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (isStreamr)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_INTERFACE)));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy) {
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_DEST)));
addPreferencesFromResource(R.xml.tunnel_gen_client_proxy);
}
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (!isHttp)
ps.removePreference(ps.findPreference(getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES)));
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
if (!isStandardOrIrc)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
@Override
protected void generalClientIrc() {
addPreferencesFromResource(R.xml.tunnel_gen_client_irc);
}
@Override
protected void generalServerHttp() {
addPreferencesFromResource(R.xml.tunnel_gen_server_http, generalCategory);
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
if (isStreamr)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT)));
}
@Override
protected void generalServerPort() {
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (isStreamr) {
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_HOST)));
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
}
@Override
protected void advanced() {
addPreferencesFromResource(R.xml.tunnel_adv);
advanced = (PreferenceScreen) ps.findPreference(
getString(R.string.TUNNEL_CAT_ADVANCED));
tunParamCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_TUNNEL_PARAMS));
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (isStreamr)
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_PROFILE)));
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (isServerOrStreamrClient)
tunParamCategory.removePreference(tunParamCategory.findPreference(getString(R.string.TUNNEL_OPT_DELAY_CONNECT)));
}
@Override
protected void advancedServer() {
addPreferencesFromResource(R.xml.tunnel_adv_server, advanced);
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp)
addPreferencesFromResource(R.xml.tunnel_adv_server_http, advanced);
else {
PreferenceCategory accessCtlCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_ACCESS_CONTROL));
accessCtlCategory.removePreference(accessCtlCategory.findPreference(getString(R.string.TUNNEL_OPT_REJECT_INPROXY)));
}
}
@Override
protected void advancedIdle() {
addPreferencesFromResource(R.xml.tunnel_adv_idle, advanced);
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (isServerOrStreamrClient)
advanced.removePreference(advanced.findPreference(getString(R.string.TUNNEL_OPT_DELAY_OPEN)));
}
@Override
protected void advancedClient() {
PreferenceCategory idleCategory = (PreferenceCategory) ps.findPreference(
getString(R.string.TUNNEL_CAT_IDLE)
);
addPreferencesFromResource(R.xml.tunnel_adv_idle_client, idleCategory);
}
@Override
protected void advancedClientHttp() {
addPreferencesFromResource(R.xml.tunnel_adv_client_http, advanced);
}
@Override
protected void advancedClientProxy() {
addPreferencesFromResource(R.xml.tunnel_adv_client_proxy, advanced);
}
@Override
protected void advancedOther() {
addPreferencesFromResource(R.xml.tunnel_adv_other, advanced);
}
}
/**
* http://stackoverflow.com/a/20806812
*
* @param id the Preferences XML to load
* @param newParent the parent PreferenceGroup to add the new Preferences to.
*/
private void addPreferencesFromResource (int id, PreferenceGroup newParent) {
PreferenceScreen screen = getPreferenceScreen();
int last = screen.getPreferenceCount();
addPreferencesFromResource(id);
while (screen.getPreferenceCount () > last) {
Preference p = screen.getPreference (last);
screen.removePreference(p); // decreases the preference count
newParent.addPreference(p);
}
}
}

View File

@ -0,0 +1,38 @@
package net.i2p.android.i2ptunnel;
import android.content.Intent;
import android.os.Bundle;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
public class TunnelDetailActivity extends I2PActivityBase implements
TunnelDetailFragment.TunnelDetailListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportActionBar().setDisplayShowTitleEnabled(false);
if (savedInstanceState == null) {
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, detailFrag).commit();
}
}
// TunnelDetailFragment.TunnelDetailListener
@Override
public void onEditTunnel(int tunnelId) {
Intent editIntent = new Intent(this, EditTunnelActivity.class);
editIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
startActivity(editIntent);
}
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
finish();
}
}

View File

@ -0,0 +1,230 @@
package net.i2p.android.i2ptunnel;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.List;
public class TunnelDetailFragment extends Fragment {
public static final String TUNNEL_ID = "tunnel_id";
TunnelDetailListener mCallback;
private TunnelControllerGroup mGroup;
private TunnelEntry mTunnel;
public static TunnelDetailFragment newInstance(int tunnelId) {
TunnelDetailFragment f = new TunnelDetailFragment();
Bundle args = new Bundle();
args.putInt(TUNNEL_ID, tunnelId);
f.setArguments(args);
return f;
}
// Container Activity must implement this interface
public interface TunnelDetailListener {
public void onEditTunnel(int tunnelId);
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (TunnelDetailListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnTunnelDeletedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
// Show error
} else if (getArguments().containsKey(TUNNEL_ID)) {
int tunnelId = getArguments().getInt(TUNNEL_ID);
mTunnel = new TunnelEntry(getActivity(),
mGroup.getControllers().get(tunnelId),
tunnelId);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
if (mTunnel != null) {
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(mTunnel.getName());
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
type.setText(mTunnel.getType());
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
description.setText(mTunnel.getDescription());
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
details.setText(mTunnel.getDetails());
View accessIfacePortLabel = v.findViewById(R.id.tunnel_access_interface_port_label);
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
View targetIfacePortLabel = v.findViewById(R.id.tunnel_target_interface_port_label);
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
switch (mTunnel.getInternalType()) {
case "httpbidirserver":
accessIfacePort.setText(mTunnel.getClientLink(false));
targetIfacePort.setText(mTunnel.getServerLink(false));
break;
case "streamrserver":
accessIfacePort.setText(mTunnel.getServerLink(false));
targetIfacePortLabel.setVisibility(View.GONE);
targetIfacePort.setVisibility(View.GONE);
break;
case "streamrclient":
accessIfacePortLabel.setVisibility(View.GONE);
accessIfacePort.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getClientLink(false));
break;
default:
if (mTunnel.isClient()) {
accessIfacePort.setText(mTunnel.getClientLink(false));
targetIfacePortLabel.setVisibility(View.GONE);
targetIfacePort.setVisibility(View.GONE);
} else {
accessIfacePortLabel.setVisibility(View.GONE);
accessIfacePort.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false));
}
}
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
autoStart.setChecked(mTunnel.startAutomatically());
}
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_i2ptunnel_detail_actions, menu);
// Disable until ticket #815 is closed
menu.findItem(R.id.action_edit_tunnel).setVisible(false);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
MenuItem start = menu.findItem(R.id.action_start_tunnel);
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
if (mTunnel != null) {
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
start.setVisible(isStopped);
start.setEnabled(isStopped);
stop.setVisible(!isStopped);
stop.setEnabled(!isStopped);
} else {
start.setVisible(false);
start.setEnabled(false);
stop.setVisible(false);
stop.setEnabled(false);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mTunnel == null)
return false;
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_start_tunnel:
mTunnel.getController().startTunnelBackground();
Toast.makeText(getActivity().getApplicationContext(),
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// Reload the action bar to change the start/stop action
getActivity().supportInvalidateOptionsMenu();
return true;
case R.id.action_stop_tunnel:
mTunnel.getController().stopTunnel();
Toast.makeText(getActivity().getApplicationContext(),
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// Reload the action bar to change the start/stop action
getActivity().supportInvalidateOptionsMenu();
return true;
case R.id.action_edit_tunnel:
mCallback.onEditTunnel(mTunnel.getId());
return true;
case R.id.action_delete_tunnel:
DialogFragment dg = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.i2ptunnel_delete_confirm_message)
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
List<String> msgs = TunnelUtil.deleteTunnel(
I2PAppContext.getGlobalContext(),
mGroup, mTunnel.getId(), null);
dialog.dismiss();
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
mCallback.onTunnelDeleted(mTunnel.getId(),
mGroup.getControllers().size());
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
};
dg.show(getFragmentManager(), "delete_tunnel_dialog");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -0,0 +1,287 @@
package net.i2p.android.i2ptunnel;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import java.util.List;
public class TunnelEntry {
public static final int RUNNING = 1;
public static final int STARTING = 2;
public static final int NOT_RUNNING = 3;
public static final int STANDBY = 4;
private final Context mContext;
private final TunnelController mController;
private final int mId;
public static TunnelEntry createNewTunnel(
Context ctx,
TunnelControllerGroup tcg,
TunnelConfig cfg) {
int tunnelId = tcg.getControllers().size();
List<String> msgs = TunnelUtil.saveTunnel(
I2PAppContext.getGlobalContext(), tcg, -1, cfg);
// TODO: Do something else with the other messages.
Toast.makeText(ctx.getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
return new TunnelEntry(ctx, cur, tunnelId);
}
public TunnelEntry(Context context, TunnelController controller, int id) {
mContext = context;
mController = controller;
mId = id;
}
public int getId() {
return mId;
}
public TunnelController getController() {
return mController;
}
/* General tunnel data for any type */
public String getName() {
if (mController.getName() != null)
return mController.getName();
else
return mContext.getResources()
.getString(R.string.i2ptunnel_new_tunnel);
}
public String getInternalType() {
return mController.getType();
}
public String getType() {
return TunnelUtil.getTypeName(mController.getType(), mContext);
}
public String getDescription() {
String rv = mController.getDescription();
if (rv != null)
return rv;
return "";
}
public boolean startAutomatically() {
return mController.getStartOnLoad();
}
public int getStatus() {
if (mController.getIsRunning()) {
if (isClient() && mController.getIsStandby())
return STANDBY;
else
return RUNNING;
} else if (mController.getIsStarting()) return STARTING;
else return NOT_RUNNING;
}
public boolean isRunning() {
switch (getStatus()) {
case STANDBY:
case RUNNING:
return true;
default:
return false;
}
}
public boolean isClient() {
return TunnelUtil.isClient(mController.getType());
}
/* Client tunnel data */
public boolean isSharedClient() {
return Boolean.parseBoolean(mController.getSharedClient());
}
/**
* Call this to see if it is okay to linkify getClientLink()
* @return true if getClientLink() can be linkified, false otherwise.
*/
public boolean isClientLinkValid() {
return ("ircclient".equals(mController.getType())) &&
mController.getListenOnInterface() != null &&
mController.getListenPort() != null;
}
/**
* @return valid host:port only if isClientLinkValid() is true
*/
public String getClientLink(boolean linkify) {
String host = getClientInterface();
String port = getClientPort();
String link = host + ":" + port;
if (linkify) {
if ("ircclient".equals(mController.getType()))
link = "irc://" + link;
}
return link;
}
public String getClientInterface() {
String rv;
if ("streamrclient".equals(mController.getType()))
rv = mController.getTargetHost();
else
rv = mController.getListenOnInterface();
return rv != null ? rv : "";
}
public String getClientPort() {
String rv = mController.getListenPort();
return rv != null ? rv : "";
}
public String getClientDestination() {
String rv;
if ("client".equals(getInternalType()) ||
"ircclient".equals(getInternalType()) ||
"streamrclient".equals(getInternalType()))
rv = mController.getTargetDestination();
else
rv = mController.getProxyList();
return rv != null ? rv : "";
}
/* Server tunnel data */
/**
* Call this to see if it is okay to linkify getServerLink()
* @return true if getServerLink() can be linkified, false otherwise.
*/
public boolean isServerLinkValid() {
return ("httpserver".equals(mController.getType()) ||
"httpbidirserver".equals(mController.getType())) &&
mController.getTargetHost() != null &&
mController.getTargetPort() != null;
}
/**
* @return valid host:port only if isServerLinkValid() is true
*/
public String getServerLink(boolean linkify) {
String host;
if ("streamrserver".equals(getInternalType()))
host = mController.getListenOnInterface();
else
host = mController.getTargetHost();
String port = mController.getTargetPort();
if (host == null) host = "";
if (port == null) port = "";
if (host.indexOf(':') >= 0)
host = '[' + host + ']';
String link = host + ":" + port;
if (linkify) {
if ("httpserver".equals(mController.getType()) ||
"httpbidirserver".equals(mController.getType()))
link = "http://" + link;
}
return link;
}
public String getDestinationBase64() {
String rv = mController.getMyDestination();
if (rv != null)
return rv;
// if not running, do this the hard way
String keyFile = mController.getPrivKeyFile();
if (keyFile != null && keyFile.trim().length() > 0) {
PrivateKeyFile pkf = new PrivateKeyFile(keyFile);
try {
Destination d = pkf.getDestination();
if (d != null)
return d.toBase64();
} catch (Exception e) {}
}
return "";
}
public String getDestHashBase32() {
String rv = mController.getMyDestHashBase32();
if (rv != null)
return rv;
return "";
}
/* Data for some client and server tunnels */
/* Other output formats */
public boolean isTunnelLinkValid() {
if (isClient()) return isClientLinkValid();
else return isServerLinkValid();
}
public String getTunnelLink(boolean linkify) {
if (isClient()) return getClientLink(linkify);
else return getServerLink(linkify);
}
public Uri getRecommendedAppForTunnel() {
int resId = 0;
if ("ircclient".equals(mController.getType()))
resId = R.string.market_irc;
if (resId > 0)
return Uri.parse(mContext.getString(resId));
else
return null;
}
public String getDetails() {
String details;
if (isClient())
details = getClientDestination();
else
details = "";
return details;
}
public Drawable getStatusIcon() {
switch (getStatus()) {
case STANDBY:
return mContext.getResources()
.getDrawable(R.drawable.ic_schedule_black_24dp);
case STARTING:
case RUNNING:
case NOT_RUNNING:
default:
return null;
}
}
public Drawable getStatusBackground() {
switch (getStatus()) {
case STANDBY:
case STARTING:
return mContext.getResources()
.getDrawable(R.drawable.tunnel_yellow);
case RUNNING:
return mContext.getResources()
.getDrawable(R.drawable.tunnel_green);
case NOT_RUNNING:
default:
return mContext.getResources()
.getDrawable(R.drawable.tunnel_red);
}
}
}

View File

@ -0,0 +1,94 @@
package net.i2p.android.i2ptunnel;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import net.i2p.android.router.R;
import java.util.List;
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
private final LayoutInflater mInflater;
public TunnelEntryAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<TunnelEntry> tunnels) {
clear();
if (tunnels != null) {
for (TunnelEntry tunnel : tunnels) {
add(tunnel);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
final TunnelEntry tunnel = getItem(position);
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
status.setImageDrawable(tunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
status.setBackgroundDrawable(tunnel.getStatusBackground());
else
status.setBackground(tunnel.getStatusBackground());
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(tunnel.getName());
TextView type = (TextView) v.findViewById(R.id.tunnel_description);
type.setText(tunnel.getDescription());
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
ifacePort.setText(tunnel.getTunnelLink(false));
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) {
View open = v.findViewById(R.id.tunnel_open);
open.setVisibility(View.VISIBLE);
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
try {
getContext().startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = tunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
getContext().startActivity(intent);
}
}
})
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
}
}
});
}
return v;
}
}

View File

@ -0,0 +1,140 @@
package net.i2p.android.i2ptunnel;
import android.content.Context;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.ArrayList;
import java.util.List;
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
private TunnelControllerGroup mGroup;
private boolean mClientTunnels;
private List<TunnelEntry> mData;
private Handler mHandler;
private TunnelControllerMonitor mMonitor;
public TunnelEntryLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
super(context);
mGroup = tcg;
mClientTunnels = clientTunnels;
mHandler = new Handler();
}
@Override
public List<TunnelEntry> loadInBackground() {
List<TunnelEntry> ret = new ArrayList<>();
List<TunnelController> controllers = mGroup.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);
if ( (mClientTunnels && tunnel.isClient()) ||
(!mClientTunnels && !tunnel.isClient()) )
ret.add(tunnel);
}
return ret;
}
@Override
public void deliverResult(List<TunnelEntry> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
if (data != null) {
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<TunnelEntry> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
// Begin monitoring the underlying data source.
mMonitor = new TunnelControllerMonitor();
mHandler.postDelayed(mMonitor, 50);
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
if (mMonitor != null) {
mHandler.removeCallbacks(mMonitor);
mMonitor = null;
}
}
@Override
public void onCanceled(List<TunnelEntry> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<TunnelEntry> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
private class TunnelControllerMonitor implements Runnable {
public void run() {
// There is no way (yet) to monitor for changes to the list of
// TunnelControllers, so just force a refresh every 10 seconds.
onContentChanged();
mHandler.postDelayed(this, 10 * 1000);
}
}
}

View File

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

View File

@ -0,0 +1,298 @@
package net.i2p.android.i2ptunnel;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.Toast;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import net.i2p.router.RouterContext;
import java.util.List;
public class TunnelListFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
public static final String SHOW_CLIENT_TUNNELS = "show_client_tunnels";
public static final String TUNNEL_WIZARD_DATA = "tunnel_wizard_data";
static final int TUNNEL_WIZARD_REQUEST = 1;
private static final int CLIENT_LOADER_ID = 1;
private static final int SERVER_LOADER_ID = 2;
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
private boolean mOnActivityCreated;
RouterContextProvider mRouterContextProvider;
OnTunnelSelectedListener mCallback;
private TunnelControllerGroup mGroup;
private TunnelEntryAdapter mAdapter;
private boolean mClientTunnels;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
private boolean mActivateOnItemClick = false;
private ImageButton mNewTunnel;
// Container Activity must implement this interface
public interface OnTunnelSelectedListener {
public void onTunnelSelected(int tunnelId);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mRouterContextProvider = (RouterContextProvider) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterContextProvider");
}
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnTunnelSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnTunnelSelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Create the list fragment's content view by calling the super method
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
listContainer.addView(listFragmentView);
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
mNewTunnel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
}
});
return v;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState
.getInt(STATE_ACTIVATED_POSITION));
}
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new TunnelEntryAdapter(getActivity());
mClientTunnels = getArguments().getBoolean(SHOW_CLIENT_TUNNELS);
setListAdapter(mAdapter);
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
else
setEmptyText(getResources().getString(
R.string.router_not_running));
}
public void onRouterConnectionReady() {
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
setEmptyText(error);
} else {
if (mClientTunnels)
setEmptyText("No configured client tunnels.");
else
setEmptyText("No configured server tunnels.");
setListShown(false);
getLoaderManager().initLoader(mClientTunnels ? CLIENT_LOADER_ID
: SERVER_LOADER_ID, null, this);
}
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
super.onListItemClick(parent, view, pos, id);
mCallback.onTunnelSelected(mAdapter.getItem(pos).getId());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
if (getRouterContext() == null) {
mNewTunnel.setVisibility(View.GONE);
menu.findItem(R.id.action_start_all_tunnels).setVisible(false);
menu.findItem(R.id.action_stop_all_tunnels).setVisible(false);
menu.findItem(R.id.action_restart_all_tunnels).setVisible(false);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
List<String> msgs;
switch (item.getItemId()) {
case R.id.action_start_all_tunnels:
msgs = mGroup.startAllControllers();
break;
case R.id.action_stop_all_tunnels:
msgs = mGroup.stopAllControllers();
break;
case R.id.action_restart_all_tunnels:
msgs = mGroup.restartAllControllers();
break;
case R.id.action_i2ptunnel_help:
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
// TODO: Do something with the other messages
if (msgs.size() > 0)
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
return true;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == TUNNEL_WIZARD_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
Bundle tunnelData = data.getExtras().getBundle(TUNNEL_WIZARD_DATA);
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), mGroup, tunnelData);
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), mGroup, cfg);
mAdapter.add(tunnel);
}
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
mActivateOnItemClick = activateOnItemClick;
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
// Duplicated from I2PFragmentBase because this extends ListFragment
private RouterContext getRouterContext() {
return mRouterContextProvider.getRouterContext();
}
// I2PFragmentBase.RouterContextUser
public void onRouterBind() {
if (mOnActivityCreated)
onRouterConnectionReady();
}
// LoaderManager.LoaderCallbacks<List<TunnelEntry>>
public Loader<List<TunnelEntry>> onCreateLoader(int id, Bundle args) {
return new TunnelEntryLoader(getActivity(), mGroup, mClientTunnels);
}
public void onLoadFinished(Loader<List<TunnelEntry>> loader,
List<TunnelEntry> data) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(null);
}
}
}

View File

@ -0,0 +1,46 @@
package net.i2p.android.i2ptunnel;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
public class TunnelWizardActivity extends AbstractWizardActivity {
@Override
protected AbstractWizardModel onCreateModel() {
return new TunnelWizardModel(this);
}
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.i2ptunnel_wizard_submit_confirm_message)
.setPositiveButton(R.string.i2ptunnel_wizard_submit_confirm_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
result.putExtra(TunnelListFragment.TUNNEL_WIZARD_DATA, mWizardModel.save());
setResult(Activity.RESULT_OK, result);
dialog.dismiss();
finish();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
};
}
}

View File

@ -0,0 +1,125 @@
package net.i2p.android.i2ptunnel;
import android.content.Context;
import android.content.res.Resources;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.model.BranchPage;
import net.i2p.android.wizard.model.Conditional;
import net.i2p.android.wizard.model.I2PDestinationPage;
import net.i2p.android.wizard.model.PageList;
import net.i2p.android.wizard.model.SingleFixedBooleanPage;
import net.i2p.android.wizard.model.SingleFixedChoicePage;
import net.i2p.android.wizard.model.SingleTextFieldPage;
public class TunnelWizardModel extends AbstractWizardModel {
public TunnelWizardModel(Context context) {
super(context);
}
@Override
protected PageList onNewRootPageList() {
Resources res = mContext.getResources();
Conditional cTunnelType = new Conditional();
Conditional cClientType = new Conditional();
Conditional cServerType = new Conditional();
return new PageList(
new BranchPage(this, res.getString(R.string.i2ptunnel_wizard_k_client_server))
.addBranch(res.getString(R.string.i2ptunnel_wizard_v_client),
new SingleFixedChoicePage(this, res.getString(R.string.i2ptunnel_wizard_k_type))
.setChoices(
res.getString(R.string.i2ptunnel_type_client),
res.getString(R.string.i2ptunnel_type_httpclient),
res.getString(R.string.i2ptunnel_type_ircclient),
res.getString(R.string.i2ptunnel_type_sockstunnel),
res.getString(R.string.i2ptunnel_type_socksirctunnel),
res.getString(R.string.i2ptunnel_type_connectclient),
res.getString(R.string.i2ptunnel_type_streamrclient))
.setRequired(true)
.makeConditional(cClientType))
.addBranch(res.getString(R.string.i2ptunnel_wizard_v_server),
new SingleFixedChoicePage(this, res.getString(R.string.i2ptunnel_wizard_k_type))
.setChoices(
res.getString(R.string.i2ptunnel_type_server),
res.getString(R.string.i2ptunnel_type_httpserver),
res.getString(R.string.i2ptunnel_type_httpbidirserver),
res.getString(R.string.i2ptunnel_type_ircserver),
res.getString(R.string.i2ptunnel_type_streamrserver))
.setRequired(true)
.makeConditional(cServerType))
.setRequired(true)
.makeConditional(cTunnelType),
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_name))
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_name))
.setRequired(true),
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_desc))
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_desc)),
new I2PDestinationPage(this, res.getString(R.string.i2ptunnel_wizard_k_dest))
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_dest))
.setRequired(true)
.setEqualAnyCondition(cClientType,
res.getString(R.string.i2ptunnel_type_client),
res.getString(R.string.i2ptunnel_type_ircclient),
res.getString(R.string.i2ptunnel_type_streamrclient)),
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_outproxies))
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_outproxies))
.setEqualAnyCondition(cClientType,
res.getString(R.string.i2ptunnel_type_httpclient),
res.getString(R.string.i2ptunnel_type_connectclient),
res.getString(R.string.i2ptunnel_type_sockstunnel),
res.getString(R.string.i2ptunnel_type_socksirctunnel)),
// Not set required because a default is specified.
// Otherwise user would need to edit the field to
// enable the Next button.
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_target_host))
.setDefault("127.0.0.1")
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_target_host))
.setEqualCondition(cClientType,
res.getString(R.string.i2ptunnel_type_streamrclient))
.setEqualAnyCondition(cServerType,
res.getString(R.string.i2ptunnel_type_server),
res.getString(R.string.i2ptunnel_type_httpserver),
res.getString(R.string.i2ptunnel_type_httpbidirserver),
res.getString(R.string.i2ptunnel_type_ircserver)),
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_target_port))
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_target_port))
.setNumeric(true)
.setRequired(true)
.setEqualCondition(cTunnelType, res.getString(R.string.i2ptunnel_wizard_v_server)),
// Not set required because a default is specified.
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_reachable_on))
.setDefault("127.0.0.1")
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_reachable_on))
.setEqualAnyCondition(cClientType,
res.getString(R.string.i2ptunnel_type_client),
res.getString(R.string.i2ptunnel_type_httpclient),
res.getString(R.string.i2ptunnel_type_ircclient),
res.getString(R.string.i2ptunnel_type_sockstunnel),
res.getString(R.string.i2ptunnel_type_socksirctunnel),
res.getString(R.string.i2ptunnel_type_connectclient))
.setEqualAnyCondition(cServerType,
res.getString(R.string.i2ptunnel_type_httpbidirserver),
res.getString(R.string.i2ptunnel_type_streamrserver)),
new SingleTextFieldPage(this, res.getString(R.string.i2ptunnel_wizard_k_binding_port))
.setDescription(res.getString(R.string.i2ptunnel_wizard_k_binding_port))
.setNumeric(true)
.setRequired(true)
.setEqualCondition(cTunnelType, res.getString(R.string.i2ptunnel_wizard_v_client))
.setEqualCondition(cServerType, res.getString(R.string.i2ptunnel_type_httpbidirserver)),
new SingleFixedBooleanPage(this, res.getString(R.string.i2ptunnel_wizard_k_auto_start))
.setDescription(res.getString(R.string.i2ptunnel_wizard_desc_auto_start))
.setRequired(true)
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,118 @@
package net.i2p.android.router;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.NetworkDatabaseFacade;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelManagerFacade;
import net.i2p.router.peermanager.ProfileOrganizer;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.stat.StatManager;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
public class I2PFragmentBase extends Fragment {
private boolean mOnActivityCreated;
RouterContextProvider mCallback;
public static final String PREF_INSTALLED_VERSION = "app.version";
public interface RouterContextUser {
public void onRouterBind();
}
// Container Activity must implement this interface
public interface RouterContextProvider {
public RouterContext getRouterContext();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (RouterContextProvider) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterContextProvider");
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
else
onRouterConnectionNotReady();
}
public void onRouterBind() {
if (mOnActivityCreated)
onRouterConnectionReady();
}
/** callback from I2PFragmentBase, override as necessary */
public void onRouterConnectionReady() {}
/** callback from I2PFragmentBase, override as necessary */
public void onRouterConnectionNotReady() {}
protected RouterContext getRouterContext() {
return mCallback.getRouterContext();
}
protected Router getRouter() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.router();
}
protected NetworkDatabaseFacade getNetDb() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.netDb();
}
protected ProfileOrganizer getProfileOrganizer() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.profileOrganizer();
}
protected TunnelManagerFacade getTunnelManager() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.tunnelManager();
}
protected CommSystemFacade getCommSystem() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.commSystem();
}
protected FIFOBandwidthLimiter getBandwidthLimiter() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.bandwidthLimiter();
}
protected StatManager getStatManager() {
RouterContext ctx = getRouterContext();
if (ctx == null)
return null;
return ctx.statManager();
}
}

View File

@ -1,22 +1,22 @@
package net.i2p.android.router.activity;
package net.i2p.android.router;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
import net.i2p.util.OrderedProperties;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
// Wouldn't this be better as a private class in MainActivity?
@ -33,47 +33,52 @@ class InitActivities {
public InitActivities(Context c) {
ctx = c;
// This needs to be changed so that we can have an alternative place
myDir = c.getFilesDir().getAbsolutePath();
myDir = Util.getFileDir(c);
_ourVersion = Util.getOurVersion(c);
}
void debugStuff() {
Util.i("java.io.tmpdir" + ": " + System.getProperty("java.io.tmpdir"));
Util.i("java.vendor" + ": " + System.getProperty("java.vendor"));
Util.i("java.version" + ": " + System.getProperty("java.version"));
Util.i("os.arch" + ": " + System.getProperty("os.arch"));
Util.i("os.name" + ": " + System.getProperty("os.name"));
Util.i("os.version" + ": " + System.getProperty("os.version"));
Util.i("user.dir" + ": " + System.getProperty("user.dir"));
Util.i("user.home" + ": " + System.getProperty("user.home"));
Util.i("user.name" + ": " + System.getProperty("user.name"));
Util.i("getFilesDir()" + ": " + myDir);
Util.i("max mem" + ": " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()));
Util.i("Package" + ": " + ctx.getPackageName());
Util.i("Version" + ": " + _ourVersion);
Util.i("MODEL" + ": " + Build.MODEL);
Util.i("DISPLAY" + ": " + Build.DISPLAY);
Util.i("VERSION" + ": " + Build.VERSION.RELEASE);
Util.i("SDK" + ": " + Build.VERSION.SDK);
Util.d("java.io.tmpdir" + ": " + System.getProperty("java.io.tmpdir"));
Util.d("java.vendor" + ": " + System.getProperty("java.vendor"));
Util.d("java.version" + ": " + System.getProperty("java.version"));
Util.d("os.arch" + ": " + System.getProperty("os.arch"));
Util.d("os.name" + ": " + System.getProperty("os.name"));
Util.d("os.version" + ": " + System.getProperty("os.version"));
Util.d("user.dir" + ": " + System.getProperty("user.dir"));
Util.d("user.home" + ": " + System.getProperty("user.home"));
Util.d("user.name" + ": " + System.getProperty("user.name"));
Util.d("getFilesDir()" + ": " + myDir);
Util.d("max mem" + ": " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()));
Util.d("Package" + ": " + ctx.getPackageName());
Util.d("Version" + ": " + _ourVersion);
Util.d("MODEL" + ": " + Build.MODEL);
Util.d("DISPLAY" + ": " + Build.DISPLAY);
Util.d("VERSION" + ": " + Build.VERSION.RELEASE);
Util.d("SDK" + ": " + Build.VERSION.SDK_INT);
}
void initialize() {
if (checkNewVersion()) {
Properties props = new Properties();
List<Properties> lProps = Util.getPropertiesFromPreferences(ctx);
Properties props = lProps.get(0);
props.setProperty("i2p.dir.temp", myDir + "/tmp");
props.setProperty("i2p.dir.pid", myDir + "/tmp");
// Time disabled in default router.config
// But lots of time problems on Android, not all carriers support NITZ
// and there was no NTP before 3.0. Tablets should be fine?
// Phones in airplane mode with wifi enabled still a problem.
// Deactivated phones in airplane mode definatly won't have correct time.
// Deactivated phones in airplane mode definitely won't have correct time.
if (Build.VERSION.SDK_INT < 11) // Honeycomb 3.0
props.setProperty("time.disabled", "false");
mergeResourceToFile(R.raw.router_config, "router.config", props);
mergeResourceToFile(R.raw.logger_config, "logger.config", null);
mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config", null);
mergeResourceToFile(R.raw.logger_config, "logger.config", lProps.get(1));
// This is not needed for now, i2ptunnel.config only contains tunnel
// settings, which can now be configured manually. We don't want to
// overwrite the user's tunnels.
//mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config", null);
copyResourceToFileIfAbsent(R.raw.i2ptunnel_config, "i2ptunnel.config");
// FIXME this is a memory hog to merge this way
mergeResourceToFile(R.raw.hosts_txt, "hosts.txt", null);
mergeResourceToFile(R.raw.more_hosts_txt, "hosts.txt", null);
@ -119,9 +124,8 @@ class InitActivities {
File certificates = new File(myDir, "certificates");
File[] allcertificates = certificates.listFiles();
if ( allcertificates != null) {
for (int i = 0; i < allcertificates.length; i++) {
File f = allcertificates[i];
Util.i("Deleting old certificate file/dir " + f);
for (File f : allcertificates) {
Util.d("Deleting old certificate file/dir " + f);
FileUtil.rmdir(f, false);
}
}
@ -137,6 +141,15 @@ class InitActivities {
System.setProperty("wrapper.logfile", myDir + "/wrapper.log");
}
/**
* @param f relative to base dir
*/
private void copyResourceToFileIfAbsent(int resID, String f) {
File file = new File(myDir, f);
if (!file.exists())
copyResourceToFile(resID, f);
}
/**
* @param f relative to base dir
*/
@ -144,7 +157,7 @@ class InitActivities {
InputStream in = null;
FileOutputStream out = null;
Util.i("Creating file " + f + " from resource");
Util.d("Creating file " + f + " from resource");
byte buf[] = new byte[4096];
try {
// Context methods
@ -163,14 +176,14 @@ class InitActivities {
}
}
/**
* @param f relative to base dir
* @param folder relative to base dir
*/
private void unzipResourceToDir(int resID, String f) {
private void unzipResourceToDir(int resID, String folder) {
InputStream in = null;
FileOutputStream out = null;
ZipInputStream zis = null;
Util.i("Creating files in '" + myDir + "/" + f + "/' from resource");
Util.d("Creating files in '" + myDir + "/" + folder + "/' from resource");
try {
// Context methods
in = ctx.getResources().openRawResource(resID);
@ -185,11 +198,17 @@ class InitActivities {
while ((count = zis.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
String filename = ze.getName();
Util.i("Creating file " + myDir + "/" + f +"/" + filename + " from resource");
byte[] bytes = baos.toByteArray();
out = new FileOutputStream(new File(myDir + "/" + f +"/" + filename));
out.write(bytes);
String name = ze.getName();
File f = new File(myDir + "/" + folder +"/" + name);
if (ze.isDirectory()) {
Util.d("Creating directory " + myDir + "/" + folder +"/" + name + " from resource");
f.mkdir();
} else {
Util.d("Creating file " + myDir + "/" + folder +"/" + name + " from resource");
byte[] bytes = baos.toByteArray();
out = new FileOutputStream(f);
out.write(bytes);
}
} catch (IOException ioe) {
} finally {
if (out != null) { try { out.close(); } catch (IOException ioe) {} out = null; }
@ -206,47 +225,14 @@ class InitActivities {
/**
* Load defaults from resource,
* then add props from file,
* and write back
* For now, do it backwards so we can override with new apks.
* When we have user configurable stuff, switch it back.
* then add props from settings,
* and write back.
*
* @param f relative to base dir
* @param props local overrides or null
* @param overrides local overrides or null
*/
public void mergeResourceToFile(int resID, String f, Properties overrides) {
InputStream in = null;
InputStream fin = null;
byte buf[] = new byte[4096];
try {
in = ctx.getResources().openRawResource(resID);
Properties props = new OrderedProperties();
// keep user settings
//DataHelper.loadProps(props, in);
try {
fin = new FileInputStream(new File(myDir, f));
DataHelper.loadProps(props, fin);
Util.i("Merging resource into file " + f);
} catch (IOException ioe) {
Util.i("Creating file " + f + " from resource");
}
// override user settings
DataHelper.loadProps(props, in);
if (overrides != null)
props.putAll(overrides);
File path = new File(myDir, f);
DataHelper.storeProps(props, path);
Util.i("Saved " + props.size() +" properties in " + f);
} catch (IOException ioe) {
} catch (Resources.NotFoundException nfe) {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
}
private void mergeResourceToFile(int resID, String f, Properties overrides) {
Util.mergeResourceToFile(ctx, myDir, f, resID, overrides, null);
}
/**
@ -262,7 +248,7 @@ class InitActivities {
fin = ctx.openFileInput(CONFIG_FILE);
DataHelper.loadProps(props, fin);
} catch (IOException ioe) {
Util.i("Looks like a new install");
Util.d("Looks like a new install");
} finally {
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
}
@ -272,12 +258,12 @@ class InitActivities {
boolean newVersion = !_ourVersion.equals(oldVersion);
if (newVersion) {
Util.i("New version " + _ourVersion);
Util.d("New version " + _ourVersion);
props.setProperty(PROP_INSTALLED_VERSION, _ourVersion);
try {
DataHelper.storeProps(props, ctx.getFileStreamPath(CONFIG_FILE));
} catch (IOException ioe) {
Util.i("Failed to write " + CONFIG_FILE);
Util.d("Failed to write " + CONFIG_FILE);
}
}
return newVersion;

View File

@ -0,0 +1,18 @@
package net.i2p.android.router;
import android.os.Bundle;
public class LicenseActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Start with the base view
if (savedInstanceState == null) {
LicenseFragment f = new LicenseFragment();
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}
}
}

View File

@ -1,15 +1,14 @@
package net.i2p.android.router.activity;
package net.i2p.android.router;
import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import net.i2p.android.router.R;
public class LicenseActivity extends ListActivity {
import net.i2p.android.router.dialog.TextResourceDialog;
public class LicenseFragment extends ListFragment {
private static final String[] names = {
"Android Application License", "Apache 2.0",
@ -26,20 +25,19 @@ public class LicenseActivity extends ListActivity {
R.raw.license_installcert_txt, R.raw.license_sha256_txt, R.raw.license_sntp_txt, R.raw.license_addressbook_txt};
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, names));
ListView lv = getListView();
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
}
// set the callback
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView parent, View view, int pos, long id) {
Intent intent = new Intent(view.getContext(), TextResourceActivity.class);
intent.putExtra(TextResourceActivity.TEXT_RESOURCE_ID, files[pos]);
startActivity(intent);
}
});
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
TextResourceDialog dialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE, names[pos]);
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, files[pos]);
dialog.setArguments(args);
dialog.show(getActivity().getSupportFragmentManager(), "license");
}
}

View File

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

View File

@ -0,0 +1,572 @@
package net.i2p.android.router;
import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.ToggleButton;
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
import net.i2p.android.router.dialog.FirstStartDialog;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.LongToggleButton;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.util.Translate;
import java.text.Collator;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class MainFragment extends I2PFragmentBase {
private Handler _handler;
private Runnable _updater;
private Runnable _oneShotUpdate;
private String _savedStatus;
private boolean _keep = true;
private boolean _startPressed = false;
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
private static final String PREF_FIRST_START = "app.router.firstStart";
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
protected static final String PROP_NEW_INSTALL = "i2p.newInstall";
protected static final String PROP_NEW_VERSION = "i2p.newVersion";
RouterControlListener mCallback;
// Container Activity must implement this interface
public interface RouterControlListener {
public boolean shouldShowOnOff();
public boolean shouldBeOn();
public void onStartRouterClicked();
public boolean onStopRouterClicked();
/** @since 0.9.19 */
public boolean isGracefulShutdownInProgress();
/** @since 0.9.19 */
public boolean onGracefulShutdownClicked();
/** @since 0.9.19 */
public boolean onCancelGracefulShutdownClicked();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (RouterControlListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterControlListener");
}
}
/**
* Called when the fragment is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Init stuff here so settings work.
if(savedInstanceState != null) {
String saved = savedInstanceState.getString("status");
if(saved != null) {
_savedStatus = saved;
}
}
_keep = true;
_handler = new Handler();
_updater = new Updater();
_oneShotUpdate = new OneShotUpdate();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, container, false);
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights);
lightImage.setImageResource(R.drawable.routerlogo_0);
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
b.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
boolean on = ((ToggleButton) view).isChecked();
if (on) {
_startPressed = true;
mCallback.onStartRouterClicked();
updateOneShot();
checkFirstStart();
} else if(mCallback.onGracefulShutdownClicked())
updateOneShot();
return true;
}
});
Button gb = (Button) v.findViewById(R.id.button_shutdown_now);
gb.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress())
if(mCallback.onStopRouterClicked())
updateOneShot();
return true;
}
});
gb = (Button) v.findViewById(R.id.button_cancel_graceful);
gb.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress())
if(mCallback.onCancelGracefulShutdownClicked())
updateOneShot();
return true;
}
});
return v;
}
@Override
public void onStart() {
super.onStart();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
if(_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
tv.setText(_savedStatus);
}
checkDialog();
_handler.postDelayed(_updater, 100);
}
@Override
public void onStop() {
super.onStop();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
}
@Override
public void onResume() {
super.onResume();
updateOneShot();
}
@Override
public void onSaveInstanceState(Bundle outState) {
if(_savedStatus != null) {
outState.putString("status", _savedStatus);
}
super.onSaveInstanceState(outState);
}
private void updateOneShot() {
_handler.postDelayed(_oneShotUpdate, 10);
}
private class OneShotUpdate implements Runnable {
public void run() {
updateVisibility();
updateStatus();
}
}
private class Updater implements Runnable {
private int counter;
private final int delay = 1000;
private final int toloop = delay / 500;
public void run() {
updateVisibility();
if(counter++ % toloop == 0) {
updateStatus();
}
//_handler.postDelayed(this, 2500);
_handler.postDelayed(this, delay);
}
}
private void updateVisibility() {
boolean showOnOff = mCallback.shouldShowOnOff();
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
b.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
boolean isOn = mCallback.shouldBeOn();
b.setChecked(isOn);
boolean isGraceful = mCallback.isGracefulShutdownInProgress();
LinearLayout gv = (LinearLayout) getActivity().findViewById(R.id.router_graceful_buttons);
gv.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
if (isOn && isGraceful) {
RouterContext ctx = getRouterContext();
if (ctx != null) {
TextView tv = (TextView) gv.findViewById(R.id.router_graceful_status);
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
tv.setText(getActivity().getResources().getString(R.string.button_router_graceful,
DataHelper.formatDuration(ms)));
} else {
tv.setText("Stopping I2P");
}
}
}
if (showOnOff && !isOn) {
// Sometimes the final state message from the RouterService
// is not received. Ensure that the state image is correct.
// TODO: Fix the race between RouterService shutdown and
// IRouterState unbinding.
updateState(State.INIT);
}
}
public boolean onBackPressed() {
RouterContext ctx = getRouterContext();
// RouterService svc = _routerService; Which is better to use?!
_keep = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
Util.d("*********************************************************");
Util.d("Back pressed, Keep? " + _keep);
Util.d("*********************************************************");
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
if(!_keep) {
Thread t = new Thread(new KillMe());
t.start();
}
}
private class KillMe implements Runnable {
public void run() {
Util.d("*********************************************************");
Util.d("KillMe started!");
Util.d("*********************************************************");
try {
Thread.sleep(500); // is 500ms long enough?
} catch(InterruptedException ex) {
}
System.exit(0);
}
}
public void updateState(State newState) {
final ImageView lightImage = (ImageView) getView().findViewById(R.id.main_lights);
if (newState == State.INIT ||
newState == State.STOPPED ||
newState == State.MANUAL_STOPPED ||
newState == State.MANUAL_QUITTED ||
newState == State.NETWORK_STOPPED) {
lightImage.setImageResource(R.drawable.routerlogo_0);
} else if (newState == State.STARTING ||
newState == State.GRACEFUL_SHUTDOWN ||
newState == State.STOPPING ||
newState == State.MANUAL_STOPPING ||
newState == State.MANUAL_QUITTING ||
newState == State.NETWORK_STOPPING) {
lightImage.setImageResource(R.drawable.routerlogo_1);
} else if (newState == State.RUNNING) {
lightImage.setImageResource(R.drawable.routerlogo_2);
} else if (newState == State.ACTIVE) {
lightImage.setImageResource(R.drawable.routerlogo_3);
} else if (newState == State.WAITING) {
lightImage.setImageResource(R.drawable.routerlogo_4);
} // Ignore unknown states.
}
private void updateStatus() {
RouterContext ctx = getRouterContext();
ScrollView sv = (ScrollView) getActivity().findViewById(R.id.main_scrollview);
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
if(!Connectivity.isConnected(getActivity())) {
// Manually set state, RouterService won't be running
updateState(State.WAITING);
vStatusText.setText("No Internet connection is available");
vStatus.setVisibility(View.VISIBLE);
sv.setVisibility(View.VISIBLE);
} else if(ctx != null) {
if(_startPressed) {
_startPressed = false;
}
// Load running tunnels
loadDestinations(ctx);
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) {
short reach = ctx.commSystem().getReachabilityStatus();
int active = ctx.commSystem().countActivePeers();
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
int inEx = ctx.tunnelManager().getFreeTunnelCount();
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
int part = ctx.tunnelManager().getParticipatingCount();
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
String jobLag = DataHelper.formatDuration((long) dLag);
String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay());
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
String netstatus;
if (reach == net.i2p.router.CommSystemFacade.STATUS_DIFFERENT) {
netstatus = "Symmetric NAT";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_HOSED) {
netstatus = "Port Failure";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_OK) {
netstatus = "OK";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_REJECT_UNSOLICITED) {
netstatus = "Firewalled";
} else {
netstatus = "Unknown";
}
String tunnelStatus = ctx.throttle().getTunnelStatus();
//ctx.commSystem().getReachabilityStatus();
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
// control total width
DecimalFormat fmt;
if(inBW >= 1000 || outBW >= 1000) {
fmt = new DecimalFormat("#0");
} else if(inBW >= 100 || outBW >= 100) {
fmt = new DecimalFormat("#0.0");
} else {
fmt = new DecimalFormat("#0.00");
}
double kBytesIn = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes() / 1024;
double kBytesOut = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes() / 1024;
// control total width
DecimalFormat kBfmt;
if(kBytesIn >= 1000 || kBytesOut >= 1000) {
kBfmt = new DecimalFormat("#0");
} else if(kBytesIn >= 100 || kBytesOut >= 100) {
kBfmt = new DecimalFormat("#0.0");
} else {
kBfmt = new DecimalFormat("#0.00");
}
String status =
"Network: " + netstatus
+ "\nPeers active/known: " + active + " / " + known
+ "\nExploratory Tunnels in/out: " + inEx + " / " + outEx
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
// Need to see if we have the participation option set to on.
// I thought there was a router method for that? I guess not! WHY NOT?
// It would be easier if we had a number to test status.
String participate = "\nParticipation: " + tunnelStatus +" (" + part + ")";
String details =
"\nBandwidth in/out: " + fmt.format(inBW) + " / " + fmt.format(outBW) + " KBps"
+ "\nData usage in/out: " + kBfmt.format(kBytesIn) + " / " + kBfmt.format(kBytesOut) + " KB"
+ "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
+ "\nJob Lag: " + jobLag
+ "\nMsg Delay: " + msgDelay
+ "\nUptime: " + uptime;
_savedStatus = status + participate + details;
vStatusText.setText(_savedStatus);
vStatus.setVisibility(View.VISIBLE);
} else {
vStatus.setVisibility(View.GONE);
}
sv.setVisibility(View.VISIBLE);
} else {
// network but no router context
vStatusText.setText("Not running");
sv.setVisibility(View.INVISIBLE);
/**
* **
* RouterService svc = _routerService; String status = "connected? "
* + Util.isConnected(this) + "\nMemory: " +
* DataHelper.formatSize(Runtime.getRuntime().totalMemory() -
* Runtime.getRuntime().freeMemory()) + "B / " +
* DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B' +
* "\nhave ctx? " + (ctx != null) + "\nhave svc? " + (svc != null) +
* "\nis bound? " + _isBound + "\nsvc state: " + (svc == null ?
* "null" : svc.getState()) + "\ncan start? " + (svc == null ?
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
* "null" : svc.canManualStop()); tv.setText(status);
* tv.setVisibility(View.VISIBLE);
***
*/
}
}
/**
* Based on net.i2p.router.web.SummaryHelper.getDestinations()
* @param ctx The RouterContext
*/
private void loadDestinations(RouterContext ctx) {
TableLayout dests = (TableLayout) getView().findViewById(R.id.main_tunnels);
dests.removeAllViews();
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients());
if (!clients.isEmpty()) {
Collections.sort(clients, new AlphaComparator(ctx));
for (Destination client : clients) {
String name = getName(ctx, client);
Hash h = client.calculateHash();
TableRow dest = new TableRow(getActivity());
dest.setPadding(16, 4, 0, 4);
// Client or server
TextView type = new TextView(getActivity());
type.setTextColor(getResources().getColor(android.R.color.primary_text_light));
type.setTypeface(Typeface.DEFAULT_BOLD);
type.setGravity(Gravity.CENTER);
if (ctx.clientManager().shouldPublishLeaseSet(h))
type.setText(R.string.char_server_tunnel);
else
type.setText(R.string.char_client_tunnel);
dest.addView(type);
// Name
TextView destName = new TextView(getActivity());
destName.setPadding(16, 0, 0, 0);
destName.setGravity(Gravity.CENTER_VERTICAL);
destName.setText(name);
dest.addView(destName);
// Status
LeaseSet ls = ctx.netDb().lookupLeaseSetLocally(h);
if (ls != null && ctx.tunnelManager().getOutboundClientTunnelCount(h) > 0) {
long timeToExpire = ls.getEarliestLeaseDate() - ctx.clock().now();
if (timeToExpire < 0) {
// red or yellow light
type.setBackgroundResource(R.drawable.tunnel_yellow);
} else {
// green light
type.setBackgroundResource(R.drawable.tunnel_green);
}
} else {
// yellow light
type.setBackgroundResource(R.drawable.tunnel_yellow);
}
dests.addView(dest);
}
} else {
TableRow empty = new TableRow(getActivity());
TextView emptyText = new TextView(getActivity());
emptyText.setText(R.string.no_client_tunnels_running);
empty.addView(emptyText);
dests.addView(empty);
}
}
/** compare translated nicknames - put "shared clients" first in the sort */
private class AlphaComparator implements Comparator<Destination> {
private String xsc;
private RouterContext _ctx;
public AlphaComparator(RouterContext ctx) {
_ctx = ctx;
xsc = _(ctx, "shared clients");
}
public int compare(Destination lhs, Destination rhs) {
String lname = getName(_ctx, lhs);
String rname = getName(_ctx, rhs);
if (lname.equals(xsc))
return -1;
if (rname.equals(xsc))
return 1;
return Collator.getInstance().compare(lname, rname);
}
}
/** translate here so collation works above */
private String getName(RouterContext ctx, Destination d) {
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
String name = (in != null ? in.getDestinationNickname() : null);
if (name == null) {
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
name = (out != null ? out.getDestinationNickname() : null);
if (name == null)
name = d.calculateHash().toBase64().substring(0,6);
else
name = _(ctx, name);
} else {
name = _(ctx, name);
}
return name;
}
private String _(RouterContext ctx, String s) {
return Translate.getString(s, ctx, "net.i2p.router.web.messages");
}
private void checkDialog() {
I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
if (configureBrowser) {
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
ab.setPref(PREF_CONFIGURE_BROWSER, false);
}
/*VersionDialog dialog = new VersionDialog();
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
if(oldVersion.equals("??")) {
// TODO Don't show this dialog until it is reworked
Bundle args = new Bundle();
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_INSTALL);
dialog.setArguments(args);
dialog.show(getActivity().getSupportFragmentManager(), "newinstall");
} else {
// TODO Don't show dialog on new version until we have something new to tell them
String currentVersion = Util.getOurVersion(getActivity());
if(!oldVersion.equals(currentVersion)) {
Bundle args = new Bundle();
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_VERSION);
dialog.setArguments(args);
dialog.show(getActivity().getSupportFragmentManager(), "newversion");
}
}*/
}
private void checkFirstStart() {
I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
if (firstStart) {
FirstStartDialog dialog = new FirstStartDialog();
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
ab.setPref(PREF_FIRST_START, false);
}
}
}

View File

@ -0,0 +1,18 @@
package net.i2p.android.router;
import android.os.Bundle;
public class NewsActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Start with the base view
if (savedInstanceState == null) {
NewsFragment f = new NewsFragment();
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}
}
}

View File

@ -0,0 +1,95 @@
package net.i2p.android.router;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import net.i2p.android.apps.NewsFetcher;
import net.i2p.android.router.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
public class NewsFragment extends I2PFragmentBase {
private long _lastChanged;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_news, container, false);
}
@Override
public void onResume() {
super.onResume();
NewsFetcher nf = NewsFetcher.getInstance();
if (nf != null) {
// Always update the status
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
tv.setText(nf.status().replace("&nbsp;", " "));
tv.setVisibility(View.VISIBLE);
}
// Only update the content if we need to
File newsFile = new File(Util.getFileDir(getActivity()), "docs/news.xml");
boolean newsExists = newsFile.exists();
if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
return;
_lastChanged = System.currentTimeMillis();
InputStream in = null;
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
byte buf[] = new byte[1024];
try {
if (newsExists) {
in = new FileInputStream(newsFile);
} else {
in = getResources().openRawResource(R.raw.initialnews_html);
}
int read;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
} catch (IOException ioe) {
System.err.println("news error " + ioe);
} catch (Resources.NotFoundException nfe) {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
String news = "";
try {
news = out.toString("UTF-8");
} catch (UnsupportedEncodingException uee) {
}
// Get SpannableStringBuilder object from HTML code
CharSequence sequence = Html.fromHtml(news);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
// Get an array of URLSpan from SpannableStringBuilder object
URLSpan[] urlSpans = strBuilder.getSpans(0, strBuilder.length(), URLSpan.class);
// Remove URLSpans with relative paths, which can't be clicked on
for (final URLSpan span : urlSpans) {
if (span.getURL().startsWith("/"))
strBuilder.removeSpan(span);
}
TextView tv = (TextView) getActivity().findViewById(R.id.news_content);
tv.setText(strBuilder);
tv.setMovementMethod(LinkMovementMethod.getInstance());
}
}

View File

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

View File

@ -0,0 +1,21 @@
package net.i2p.android.router.addressbook;
import net.i2p.data.Destination;
public class AddressEntry {
private final String mHostName;
private final Destination mDest;
public AddressEntry(String hostName, Destination dest) {
mHostName = hostName;
mDest = dest;
}
public String getHostName() {
return mHostName;
}
public Destination getDestination() {
return mDest;
}
}

View File

@ -0,0 +1,40 @@
package net.i2p.android.router.addressbook;
import java.util.List;
import net.i2p.android.router.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class AddressEntryAdapter extends ArrayAdapter<AddressEntry> {
private final LayoutInflater mInflater;
public AddressEntryAdapter(Context context) {
super(context, R.layout.listitem_text);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<AddressEntry> addresses) {
clear();
if (addresses != null) {
for (AddressEntry address : addresses) {
add(address);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_text, parent, false);
AddressEntry address = getItem(position);
TextView text = (TextView) v.findViewById(R.id.text);
text.setText(address.getHostName());
return v;
}
}

View File

@ -0,0 +1,139 @@
package net.i2p.android.router.addressbook;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.android.router.util.Util;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
private I2PFragmentBase.RouterContextProvider mRContextProvider;
private String mBook;
private String mFilter;
private List<AddressEntry> mData;
public AddressEntryLoader(Context context, I2PFragmentBase.RouterContextProvider rContextProvider,
String book, String filter) {
super(context);
mRContextProvider = rContextProvider;
mBook = book;
mFilter = filter;
}
@Override
public List<AddressEntry> loadInBackground() {
RouterContext routerContext = mRContextProvider.getRouterContext();
if (routerContext == null)
return null;
// get the names
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
Util.d("NamingService: " + ns.getName());
// After router shutdown we get nothing... why?
List<AddressEntry> ret = new ArrayList<>();
Map<String, Destination> names = new TreeMap<>();
Properties searchProps = new Properties();
// Needed for HostsTxtNamingService
searchProps.setProperty("file", mBook);
if (mFilter != null && mFilter.length() > 0)
searchProps.setProperty("search", mFilter);
names.putAll(ns.getEntries(searchProps));
for (String hostName : names.keySet())
ret.add(new AddressEntry(hostName, names.get(hostName)));
return ret;
}
@Override
public void deliverResult(List<AddressEntry> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
if (data != null) {
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<AddressEntry> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
}
@Override
public void onCanceled(List<AddressEntry> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<AddressEntry> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
}

View File

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

View File

@ -0,0 +1,44 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
public class AddressbookAddWizardActivity extends AbstractWizardActivity {
@Override
protected AbstractWizardModel onCreateModel() {
return new AddressbookAddWizardModel(this);
}
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage("Add to private addressbook?")
.setPositiveButton("Add",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
setResult(Activity.RESULT_OK, result);
result.putExtra(AddressbookFragment.ADD_WIZARD_DATA, mWizardModel.save());
dialog.dismiss();
finish();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
};
}
}

View File

@ -0,0 +1,31 @@
package net.i2p.android.router.addressbook;
import android.content.Context;
import android.content.res.Resources;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.model.I2PB64DestinationPage;
import net.i2p.android.wizard.model.PageList;
import net.i2p.android.wizard.model.SingleTextFieldPage;
public class AddressbookAddWizardModel extends AbstractWizardModel {
public AddressbookAddWizardModel(Context context) {
super(context);
}
@Override
protected PageList onNewRootPageList() {
Resources res = mContext.getResources();
return new PageList(
new SingleTextFieldPage(this, res.getString(R.string.addressbook_add_wizard_k_name))
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_name))
.setRequired(true),
new I2PB64DestinationPage(this, res.getString(R.string.addressbook_add_wizard_k_destination))
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_destination))
.setRequired(true)
);
}
}

View File

@ -0,0 +1,288 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.addressbook.Daemon;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.android.router.R;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.client.naming.NamingService;
import net.i2p.router.RouterContext;
import java.util.List;
public class AddressbookFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
public static final String BOOK_NAME = "book_name";
public static final String ROUTER_BOOK = "hosts.txt";
public static final String PRIVATE_BOOK = "privatehosts.txt";
public static final String ADD_WIZARD_DATA = "add_wizard_data";
private static final int ADD_WIZARD_REQUEST = 1;
private static final int ROUTER_LOADER_ID = 1;
private static final int PRIVATE_LOADER_ID = 2;
private boolean mOnActivityCreated;
private RouterContextProvider mRouterContextProvider;
private OnAddressSelectedListener mCallback;
private AddressEntryAdapter mAdapter;
private String mBook;
private String mCurFilter;
private ImageButton mAddToAddressbook;
// Set in onActivityResult()
private Intent mAddWizardData;
// Container Activity must implement this interface
public interface OnAddressSelectedListener {
public void onAddressSelected(CharSequence host);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mRouterContextProvider = (RouterContextProvider) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterContextProvider");
}
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnAddressSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnAddressSelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Create the list fragment's content view by calling the super method
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
listContainer.addView(listFragmentView);
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
startActivityForResult(wi, ADD_WIZARD_REQUEST);
}
});
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new AddressEntryAdapter(getActivity());
mBook = getArguments().getString(BOOK_NAME);
// Set adapter to null before setting the header
setListAdapter(null);
TextView v = new TextView(getActivity());
v.setTag("addressbook_header");
getListView().addHeaderView(v);
setListAdapter(mAdapter);
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
else
setEmptyText(getResources().getString(
R.string.router_not_running));
}
public void onRouterConnectionReady() {
// Show actions
if (mSearchAddressbook != null)
mSearchAddressbook.setVisible(true);
if (mAddToAddressbook != null && mAddToAddressbook.getVisibility() != View.VISIBLE)
mAddToAddressbook.setVisibility(View.VISIBLE);
if (mAddWizardData != null) {
// Save the new entry
Bundle entryData = mAddWizardData.getExtras().getBundle(ADD_WIZARD_DATA);
NamingService ns = NamingServiceUtil.getNamingService(getRouterContext(), mBook);
boolean success = NamingServiceUtil.addFromWizard(getActivity(), ns, entryData, false);
if (success) {
// Reload the list
setListShown(false);
getLoaderManager().restartLoader(PRIVATE_LOADER_ID, null, this);
}
} else {
setEmptyText("No hosts in address book " + mBook);
setListShown(false);
getLoaderManager().initLoader(PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
}
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
CharSequence host = ((TextView) view).getText();
mCallback.onAddressSelected(host);
}
private MenuItem mSearchAddressbook;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
// Hide until needed
if (getRouterContext() == null) {
mSearchAddressbook.setVisible(false);
if (mAddToAddressbook != null)
mAddToAddressbook.setVisibility(View.GONE);
}
// Only allow adding to private book
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
mAddToAddressbook.setVisibility(View.GONE);
mAddToAddressbook = null;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_reload_subscriptions:
Daemon.wakeup();
Toast.makeText(getActivity(), "Reloading subscriptions...",
Toast.LENGTH_SHORT).show();
return true;
case R.id.action_addressbook_settings:
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
startActivity(si);
return true;
case R.id.action_addressbook_help:
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ADD_WIZARD_REQUEST &&
resultCode == Activity.RESULT_OK &&
PRIVATE_BOOK.equals(mBook)) {
mAddWizardData = data;
}
}
public void filterAddresses(String query) {
mCurFilter = !TextUtils.isEmpty(query) ? query : null;
if (getRouterContext() != null && mAdapter != null) {
setListShown(false);
getLoaderManager().restartLoader(PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID, null, this);
}
}
// Duplicated from I2PFragmentBase because this extends ListFragment
private RouterContext getRouterContext() {
return mRouterContextProvider.getRouterContext();
}
// I2PFragmentBase.RouterContextUser
public void onRouterBind() {
if (mOnActivityCreated)
onRouterConnectionReady();
}
// LoaderManager.LoaderCallbacks<List<AddressEntry>>
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
return new AddressEntryLoader(getActivity(),
mRouterContextProvider, mBook, mCurFilter);
}
public void onLoadFinished(Loader<List<AddressEntry>> loader,
List<AddressEntry> data) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
if (data == null)
setEmptyText(getResources().getString(
R.string.router_not_running));
else {
mAdapter.setData(data);
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
if (mCurFilter != null)
v.setText(getActivity().getResources().getString(
R.string.addressbook_search_header,
data.size()));
else
v.setText("");
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
}
public void onLoaderReset(Loader<List<AddressEntry>> loader) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
mAdapter.setData(null);
}
}
}

View File

@ -1,23 +1,25 @@
package net.i2p.android.router.activity;
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import net.i2p.android.router.R;
import net.i2p.util.FileUtil;
public class AddressbookSettingsActivity extends Activity {
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
protected EditText text_content_subscriptions;
protected Button btn_save_subscriptions;
public class AddressbookSettingsActivity extends ActionBarActivity {
private EditText text_content_subscriptions;
private Button btn_save_subscriptions;
private String filename = "/addressbook/subscriptions.txt";
private File i2pDir;
@ -25,6 +27,12 @@ public class AddressbookSettingsActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_addressbook_settings);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
text_content_subscriptions = (EditText) findViewById(R.id.subscriptions_content);
btn_save_subscriptions = (Button) findViewById(R.id.button_save_subscriptions);
init_actions();
@ -32,12 +40,6 @@ public class AddressbookSettingsActivity extends Activity {
load();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_addressbook_settings, menu);
return true;
}
private void init_actions() {
btn_save_subscriptions.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
@ -65,7 +67,6 @@ public class AddressbookSettingsActivity extends Activity {
return false;
}
@SuppressWarnings("CallToThreadDumpStack")
private boolean save() {
//
String content = text_content_subscriptions.getText().toString();

View File

@ -0,0 +1,52 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import net.i2p.android.router.util.Util;
public class AboutDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
View view = li.inflate(R.layout.fragment_dialog_about, null);
final String currentVersion = Util.getOurVersion(getActivity());
TextView tv = (TextView)view.findViewById(R.id.about_version);
tv.setText(currentVersion);
tv = (TextView)view.findViewById(R.id.url_project);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_android_bugs);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_android_volunteer);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_donate);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.menu_about)
.setView(view)
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent lic = new Intent(getActivity(), LicenseActivity.class);
startActivity(lic);
}
});
return b.create();
}
}

View File

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

View File

@ -0,0 +1,33 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
public class FirstStartDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
View view = li.inflate(R.layout.fragment_dialog_first_start, null);
TextView tv = (TextView)view.findViewById(R.id.url_faq);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_irc_i2p);
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.first_start_title)
.setView(view);
return b.create();
}
}

View File

@ -1,39 +1,46 @@
package net.i2p.android.router.activity;
package net.i2p.android.router.dialog;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
/**
* Display a raw text resource.
* The resource ID must be passed as an extra in the intent.
*/
public class TextResourceActivity extends I2PActivityBase {
public class TextResourceDialog extends DialogFragment {
final static String TEXT_RESOURCE_ID = "text_resource_id";
public static final String TEXT_DIALOG_TITLE = "text_title";
public final static String TEXT_RESOURCE_ID = "text_resource_id";
@Override
public void onCreate(Bundle savedInstanceState)
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.text_resource);
TextView tv = (TextView) findViewById(R.id.text_resource_text);
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
Intent intent = getIntent();
int id = intent.getIntExtra(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
String title = getArguments().getString(TEXT_DIALOG_TITLE);
if (title != null)
getDialog().setTitle(title);
int id = getArguments().getInt(TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
if (id == R.raw.releasenotes_txt)
tv.setText("Release Notes for Release " + Util.getOurVersion(this) + "\n\n" +
tv.setText("Release Notes for Release " + Util.getOurVersion(getActivity()) + "\n\n" +
getResourceAsString(id));
else
tv.setText(getResourceAsString(id));
return v;
}
private String getResourceAsString(int id) {
@ -47,10 +54,8 @@ public class TextResourceActivity extends I2PActivityBase {
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
} catch (IOException ioe) {
System.err.println("resource error " + ioe);
} catch (Resources.NotFoundException nfe) {
System.err.println("resource error " + nfe);
} catch (IOException | Resources.NotFoundException re) {
System.err.println("resource error " + re);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}

View File

@ -0,0 +1,93 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.MainFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
public class VersionDialog extends DialogFragment {
public static final String DIALOG_TYPE = "dialog_type";
public static final int DIALOG_NEW_INSTALL = 0;
public static final int DIALOG_NEW_VERSION = 1;
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
final String currentVersion = Util.getOurVersion(getActivity());
Dialog rv;
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
int id = getArguments().getInt(DIALOG_TYPE);
switch(id) {
case DIALOG_NEW_INSTALL:
b.setMessage(R.string.welcome_new_install)
.setCancelable(false)
.setPositiveButton("Dismiss", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
I2PActivityBase ab = (I2PActivityBase) getActivity();
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
dialog.dismiss();
}
}).setNegativeButton(R.string.label_release_notes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
I2PActivityBase ab = (I2PActivityBase) getActivity();
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
dialog.dismiss();
TextResourceDialog f = new TextResourceDialog();
Bundle args = new Bundle();
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
f.setArguments(args);
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, f)
.addToBackStack(null)
.commit();
}
});
rv = b.create();
break;
case DIALOG_NEW_VERSION:
default:
b.setMessage(getResources().getString(R.string.welcome_new_version) +
" " + currentVersion)
.setCancelable(true)
.setPositiveButton("Dismiss", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
I2PActivityBase ab = (I2PActivityBase) getActivity();
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
dialog.dismiss();
}
}).setNegativeButton(R.string.label_release_notes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
I2PActivityBase ab = (I2PActivityBase) getActivity();
ab.setPref(MainFragment.PREF_INSTALLED_VERSION, currentVersion);
dialog.dismiss();
TextResourceDialog f = new TextResourceDialog();
Bundle args = new Bundle();
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
f.setArguments(args);
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, f)
.addToBackStack(null)
.commit();
}
});
rv = b.create();
break;
}
return rv;
}
}

View File

@ -0,0 +1,141 @@
package net.i2p.android.router.log;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class LogActivity extends I2PActivityBase implements
LogFragment.OnEntrySelectedListener {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private static final String SELECTED_LEVEL = "selected_level";
private String[] mLevels;
private Spinner mSpinner;
@Override
protected boolean canUseTwoPanes() {
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLevels = getResources().getStringArray(R.array.log_level_list);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
R.array.log_level_list, android.R.layout.simple_spinner_dropdown_item));
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selectLevel(i);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
if (savedInstanceState != null) {
int selected = savedInstanceState.getInt(SELECTED_LEVEL);
mSpinner.setSelection(selected);
} else
selectLevel(0);
}
private void selectLevel(int i) {
String level = mLevels[i];
LogFragment f = LogFragment.newInstance(level);
// In two-pane mode, list items should be given the
// 'activated' state when touched.
if (mTwoPane)
f.setActivateOnItemClick(true);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f, level).commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_base_actions, menu);
// Help menu not needed (yet), hide
// TODO: Unhide when Help finished
//menu.findItem(R.id.menu_help).setVisible(false);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(LogActivity.this, SettingsActivity.class);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
intent.setAction("net.i2p.android.router.PREFS_LOGGING");
} else { // TODO: Test if this works, fix if not
Bundle args = new Bundle();
args.putString("settings", "logging");
intent.putExtras(args);
}
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_LEVEL, mSpinner.getSelectedItemPosition());
}
// LogFragment.OnEntrySelectedListener
public void onEntrySelected(String entry) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
LogDetailFragment detailFrag = LogDetailFragment.newInstance(entry);
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, detailFrag).commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, LogDetailActivity.class);
detailIntent.putExtra(LogDetailFragment.LOG_ENTRY, entry);
startActivity(detailIntent);
}
}
}

View File

@ -0,0 +1,24 @@
package net.i2p.android.router.log;
import java.util.List;
import net.i2p.android.router.R;
import android.content.Context;
import android.widget.ArrayAdapter;
public class LogAdapter extends ArrayAdapter<String> {
public LogAdapter(Context context) {
super(context, R.layout.listitem_logs);
}
public void setData(List<String> entries) {
clear();
if (entries != null) {
for (String entry : entries) {
add(entry);
}
}
}
}

View File

@ -0,0 +1,22 @@
package net.i2p.android.router.log;
import android.os.Bundle;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
public class LogDetailActivity extends I2PActivityBase {
LogDetailFragment mDetailFrag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (savedInstanceState == null) {
String entry = getIntent().getStringExtra(LogDetailFragment.LOG_ENTRY);
mDetailFrag = LogDetailFragment.newInstance(entry);
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, mDetailFrag).commit();
}
}
}

View File

@ -0,0 +1,77 @@
package net.i2p.android.router.log;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
public class LogDetailFragment extends I2PFragmentBase {
public static final String LOG_ENTRY = "log_entry";
private String mEntry;
public static LogDetailFragment newInstance (String entry) {
LogDetailFragment f = new LogDetailFragment();
Bundle args = new Bundle();
args.putString(LOG_ENTRY, entry);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_log_entry, container, false);
mEntry = getArguments().getString(LOG_ENTRY);
TextView tv = (TextView) v.findViewById(R.id.log_entry);
tv.setMovementMethod(new ScrollingMovementMethod());
tv.setText(mEntry);
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_log_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_copy_logs:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mEntry);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
getString(R.string.i2p_android_logs), mEntry);
clipboard.setPrimaryClip(clip);
}
Toast.makeText(getActivity(), R.string.logs_copied_to_clipboard, Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -0,0 +1,257 @@
package net.i2p.android.router.log;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import java.util.ArrayList;
import java.util.List;
public class LogFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<String>> {
public static final String LOG_LEVEL = "log_level";
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
private static final int LEVEL_ERROR = 1;
private static final int LEVEL_ALL = 2;
OnEntrySelectedListener mEntrySelectedCallback;
private final List<String> mLogEntries = new ArrayList<>();
private LogAdapter mAdapter;
private TextView mHeaderView;
private String mLogLevel;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
private boolean mActivateOnItemClick = false;
private MenuItem mCopyLogs;
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(String entry);
}
public static LogFragment newInstance(String level) {
LogFragment f = new LogFragment();
Bundle args = new Bundle();
args.putString(LOG_LEVEL, level);
f.setArguments(args);
return f;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEntrySelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState
.getInt(STATE_ACTIVATED_POSITION));
}
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
mAdapter = new LogAdapter(getActivity());
mLogLevel = getArguments().getString(LOG_LEVEL);
// set the header
mHeaderView = (TextView) getActivity().getLayoutInflater().inflate(R.layout.logs_header, null);
getListView().addHeaderView(mHeaderView, "", false);
setListAdapter(mAdapter);
I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null) {
setEmptyText("ERROR".equals(mLogLevel) ?
"No error messages" : "No messages");
setListShown(false);
getLoaderManager().initLoader("ERROR".equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL, null, this);
} else
setEmptyText(getResources().getString(
R.string.router_not_running));
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
super.onListItemClick(parent, view, pos, id);
String entry = mAdapter.getItem(pos - 1);
mEntrySelectedCallback.onEntrySelected(entry);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_log_actions, menu);
mCopyLogs = menu.findItem(R.id.action_copy_logs);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
mCopyLogs.setVisible(I2PAppContext.getCurrentContext() != null);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_copy_logs:
String logText = "";
synchronized (mLogEntries) {
for (String logEntry : mLogEntries) {
logText += logEntry;
}
}
boolean isError = "ERROR".equals(mLogLevel);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(logText);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
isError ? getString(R.string.i2p_android_error_logs) : getString(R.string.i2p_android_logs),
logText);
clipboard.setPrimaryClip(clip);
}
int textId;
if (isError)
textId = R.string.error_logs_copied_to_clipboard;
else
textId = R.string.logs_copied_to_clipboard;
Toast.makeText(getActivity(), textId, Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
mActivateOnItemClick = activateOnItemClick;
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
/** fixme plurals */
private static String getHeader(int sz, boolean errorsOnly) {
if (errorsOnly) {
if (sz == 0)
return "No error messages";
if (sz == 1)
return "1 error message";
return sz + " error messages, newest first";
}
if (sz == 0)
return "No messages";
if (sz == 1)
return "1 message";
return sz + " messages, newest first";
}
// LoaderManager.LoaderCallbacks<List<String>>
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
return new LogLoader(getActivity(),
I2PAppContext.getCurrentContext(), mLogLevel);
}
public void onLoadFinished(Loader<List<String>> loader,
List<String> data) {
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL)) {
synchronized (mLogEntries) {
mLogEntries.clear();
mLogEntries.addAll(data);
}
mAdapter.setData(data);
String header = getHeader(data.size(), ("ERROR".equals(mLogLevel)));
mHeaderView.setText(header);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
public void onLoaderReset(Loader<List<String>> loader) {
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL)) {
mAdapter.setData(null);
}
}
}

View File

@ -0,0 +1,132 @@
package net.i2p.android.router.log;
import java.util.Collections;
import java.util.List;
import net.i2p.I2PAppContext;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public class LogLoader extends AsyncTaskLoader<List<String>> {
private I2PAppContext mCtx;
private String mLogLevel;
private List<String> mData;
private static final int MAX_LOG_LENGTH = 250;
public LogLoader(Context context, I2PAppContext ctx, String logLevel) {
super(context);
mCtx = ctx;
mLogLevel = logLevel;
}
@Override
public List<String> loadInBackground() {
List<String> msgs;
if ("ERROR".equals(mLogLevel)) {
msgs = mCtx.logManager().getBuffer().getMostRecentCriticalMessages();
} else {
msgs = mCtx.logManager().getBuffer().getMostRecentMessages();
}
int sz = msgs.size();
if (sz > 1)
Collections.reverse(msgs);
if (sz > 0 && mData != null) {
String oldNewest = mData.size() > 0 ? mData.get(0) : null;
for (int i = 0; i < sz; i++) {
String newItem = msgs.get(i);
if (newItem.equals(oldNewest))
break;
mData.add(i, newItem);
}
int newSz = mData.size();
for (int i = newSz - 1; i > MAX_LOG_LENGTH; i--) {
mData.remove(i);
}
}
return msgs;
}
@Override
public void deliverResult(List<String> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
if (data != null) {
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<String> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
}
@Override
public void onCanceled(List<String> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<String> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
}

View File

@ -0,0 +1,120 @@
package net.i2p.android.router.netdb;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.data.Hash;
public class NetDbActivity extends I2PActivityBase implements
NetDbListFragment.OnEntrySelectedListener {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private static final String SELECTED_PAGE = "selected_page";
private static final int PAGE_STATS = 0;
private static final int PAGE_ROUTERS = 1;
private Spinner mSpinner;
@Override
protected boolean canUseTwoPanes() {
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
R.array.netdb_pages, android.R.layout.simple_spinner_dropdown_item));
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selectPage(i);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState != null) {
int selected = savedInstanceState.getInt(SELECTED_PAGE);
mSpinner.setSelection(selected);
} else
selectPage(PAGE_STATS);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
}
private void selectPage(int page) {
Fragment f;
if (page == PAGE_STATS)
f = new NetDbSummaryPagerFragment();
else {
f = new NetDbListFragment();
Bundle args = new Bundle();
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, page == PAGE_ROUTERS);
f.setArguments(args);
// In two-pane mode, list items should be given the
// 'activated' state when touched.
if (mTwoPane)
((NetDbListFragment) f).setActivateOnItemClick(true);
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f).commit();
}
// NetDbListFragment.OnEntrySelectedListener
public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
NetDbDetailFragment detailFrag = NetDbDetailFragment.newInstance(
isRouterInfo, entryHash);
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, detailFrag).commit();
// If we are coming from a LS to a RI, change the tab
int currentTab = mSpinner.getSelectedItemPosition();
if (isRouterInfo && currentTab != PAGE_ROUTERS)
selectPage(PAGE_ROUTERS);
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
entryHash.toBase64());
startActivity(detailIntent);
}
}
}

View File

@ -0,0 +1,44 @@
package net.i2p.android.router.netdb;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataFormatException;
import net.i2p.data.Hash;
import android.content.Intent;
import android.os.Bundle;
public class NetDbDetailActivity extends I2PActivityBase implements
NetDbListFragment.OnEntrySelectedListener {
NetDbDetailFragment mDetailFrag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (savedInstanceState == null) {
boolean isRI = getIntent().getBooleanExtra(NetDbDetailFragment.IS_RI, true);
Hash hash = new Hash();
try {
hash.fromBase64(getIntent().getStringExtra(NetDbDetailFragment.ENTRY_HASH));
mDetailFrag = NetDbDetailFragment.newInstance(isRI, hash);
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, mDetailFrag).commit();
} catch (DataFormatException e) {
Util.e(e.toString());
}
}
}
// NetDbListFragment.OnEntrySelectedListener
public void onEntrySelected(boolean isRouterInfo, Hash entryHash) {
// Start the detail activity for the selected item ID.
Intent detailIntent = new Intent(this, NetDbDetailActivity.class);
detailIntent.putExtra(NetDbDetailFragment.IS_RI, isRouterInfo);
detailIntent.putExtra(NetDbDetailFragment.ENTRY_HASH,
entryHash.toBase64());
startActivity(detailIntent);
}
}

View File

@ -0,0 +1,246 @@
package net.i2p.android.router.netdb;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.netdb.NetDbListFragment.OnEntrySelectedListener;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.Lease;
import net.i2p.data.LeaseSet;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import java.util.Map;
public class NetDbDetailFragment extends I2PFragmentBase {
public static final String IS_RI = "is_routerinfo";
public static final String ENTRY_HASH = "entry_hash";
OnEntrySelectedListener mEntrySelectedCallback;
private NetDbEntry mEntry;
public static NetDbDetailFragment newInstance(boolean isRI, Hash hash) {
NetDbDetailFragment f = new NetDbDetailFragment();
Bundle args = new Bundle();
args.putBoolean(IS_RI, isRI);
args.putString(ENTRY_HASH, hash.toBase64());
f.setArguments(args);
return f;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEntrySelectedListener");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v;
if (getArguments().getBoolean(IS_RI)) {
v = inflater.inflate(R.layout.fragment_netdb_router_detail, container, false);
} else {
v = inflater.inflate(R.layout.fragment_netdb_leaseset_detail, container, false);
}
return v;
}
@Override
public void onRouterConnectionReady() {
if (getRouterContext() != null && mEntry == null)
loadEntry();
}
private void loadEntry() {
if (getNetDb().isInitialized()) {
Hash hash = new Hash();
try {
hash.fromBase64(getArguments().getString(ENTRY_HASH));
if (getArguments().getBoolean(IS_RI)) {
// Load RouterInfo
RouterInfo ri = getNetDb().lookupRouterInfoLocally(hash);
if (ri != null)
loadRouterInfo(ri);
// TODO: Handle null case in UI
} else {
// Load LeaseSet
LeaseSet ls = getNetDb().lookupLeaseSetLocally(hash);
if (ls != null)
loadLeaseSet(ls);
// TODO: Handle null case in UI
}
} catch (DataFormatException e) {
Util.e(e.toString());
}
}
}
private void loadRouterInfo(RouterInfo ri) {
mEntry = NetDbEntry.fromRouterInfo(getRouterContext(), ri);
if (mEntry.isUs())
getActivity().setTitle("Our info");
else
getActivity().setTitle("Peer info");
TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
entryHash.setText(mEntry.getHash().toBase64());
if (mEntry.isUs() && getRouter().isHidden()) {
TextView pubLabel = (TextView) getView().findViewById(R.id.label_ri_published);
pubLabel.setText("Hidden, Updated:");
}
TextView published = (TextView) getView().findViewById(R.id.ri_published);
long age = getRouterContext().clock().now() - ri.getPublished();
if (age > 0) {
published.setText(DataHelper.formatDuration(age) + " ago");
} else {
// shouldn't happen
published.setText(DataHelper.formatDuration(0-age) + " ago???");
}
LinearLayout addresses = (LinearLayout) getView().findViewById(R.id.ri_addresses);
for (RouterAddress addr : ri.getAddresses()) {
addAddress(addresses, addr);
}
TableLayout stats = (TableLayout) getView().findViewById(R.id.ri_stats);
Map<Object, Object> p = ri.getOptionsMap();
for (Map.Entry<Object,Object> e : p.entrySet()) {
String key = (String)e.getKey();
String val = (String)e.getValue();
addTableRow(stats, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
}
}
private void addAddress(LinearLayout addresses, RouterAddress addr) {
TableLayout table = new TableLayout(getActivity());
String style = addr.getTransportStyle();
addTableRow(table, "Style", style);
int cost = addr.getCost();
if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
addTableRow(table, "cost", ""+cost);
Map<Object, Object> p = addr.getOptionsMap();
for (Map.Entry<Object,Object> e : p.entrySet()) {
String key = (String)e.getKey();
String val = (String)e.getValue();
addTableRow(table, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
}
addresses.addView(table);
}
private void loadLeaseSet(LeaseSet ls) {
mEntry = NetDbEntry.fromLeaseSet(getRouterContext(), ls);
getActivity().setTitle("LeaseSet");
TextView nickname = (TextView) getView().findViewById(R.id.ls_nickname);
nickname.setText(mEntry.getNickname());
TextView type = (TextView) getView().findViewById(R.id.ls_type);
if (mEntry.isLocal()) {
if (mEntry.isUnpublished())
type.setText("Local Unpublished Destination");
else
type.setText("Local Destination");
}
TextView entryHash = (TextView) getView().findViewById(R.id.dbentry_hash);
entryHash.setText(mEntry.getHash().toBase64());
TextView expiry = (TextView) getView().findViewById(R.id.ls_expiry);
long exp = ls.getLatestLeaseDate() - getRouterContext().clock().now();
if (exp > 0) {
expiry.setText(DataHelper.formatDuration(exp));
} else {
TextView expiryLabel = (TextView) getView().findViewById(R.id.label_ls_expiry);
expiryLabel.setText("Expired:");
expiry.setText(DataHelper.formatDuration(exp) + " ago");
}
LinearLayout leases = (LinearLayout) getView().findViewById(R.id.ls_leases);
for (int i = 0; i < ls.getLeaseCount(); i++) {
Lease lease = ls.getLease(i);
addLease(leases, lease, i);
}
}
private void addLease(LinearLayout leases, Lease lease, int i) {
TableLayout table = new TableLayout(getActivity());
addTableRow(table, "Lease", ""+(i+1));
TableRow gateway = new TableRow(getActivity());
gateway.setPadding(10, 0, 0, 0);
TextView gatewayLabel = new TextView(getActivity());
gatewayLabel.setText("Gateway");
Button gatewayButton = new Button(getActivity());
gatewayButton.setText(lease.getGateway().toBase64().substring(0, 4));
final Hash gatewayHash = lease.getGateway();
gatewayButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
mEntrySelectedCallback.onEntrySelected(
true, gatewayHash);
}
});
gateway.addView(gatewayLabel);
gateway.addView(gatewayButton);
table.addView(gateway);
addTableRow(table, "Tunnel", ""+lease.getTunnelId().getTunnelId());
leases.addView(table);
}
private void addTableRow(TableLayout table, String key, String val) {
TableRow row;
TextView tl1, tl2;
row = new TableRow(getActivity());
row.setPadding(10, 0, 0, 0);
tl1 = new TextView(getActivity());
tl2 = new TextView(getActivity());
tl1.setText(key);
tl2.setText(val);
row.addView(tl1);
row.addView(tl2);
table.addView(row);
}
}

View File

@ -0,0 +1,124 @@
package net.i2p.android.router.netdb;
import java.lang.reflect.Field;
import net.i2p.android.router.R;
import net.i2p.data.DatabaseEntry;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings;
public class NetDbEntry {
private final boolean mIsRI;
private final DatabaseEntry mEntry;
private final boolean mIsUs;
private final String mCountry;
private final String mNick;
private final boolean mLocal;
private final boolean mUnpublished;
public static NetDbEntry fromRouterInfo(RouterContext ctx, RouterInfo ri) {
Hash us = ctx.routerHash();
boolean isUs = ri.getHash().equals(us);
// XXX Disabled, no GeoIP file
String country = "";//ctx.commSystem().getCountry(ri.getIdentity().getHash());
return new NetDbEntry(ri, isUs, country);
}
public static NetDbEntry fromLeaseSet(RouterContext ctx, LeaseSet ls) {
String nick;
boolean local = false;
boolean unpublished = false;
Destination dest = ls.getDestination();
Hash key = dest.calculateHash();
if (ctx.clientManager().isLocal(dest)) {
local = true;
if (! ctx.clientManager().shouldPublishLeaseSet(key))
unpublished = true;
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(key);
if (in != null && in.getDestinationNickname() != null)
nick = in.getDestinationNickname();
else
nick = dest.toBase64().substring(0, 6);
} else {
String host = ctx.namingService().reverseLookup(dest);
if (host != null)
nick = host;
else
nick = dest.toBase64().substring(0, 6);
}
return new NetDbEntry(ls, nick, local, unpublished);
}
private NetDbEntry(RouterInfo ri,
boolean isUs, String country) {
mIsRI = true;
mEntry = ri;
mIsUs = isUs;
mCountry = country;
mNick = "";
mLocal = mUnpublished = false;
}
private NetDbEntry(LeaseSet ls,
String nick, boolean local, boolean unpublished) {
mIsRI = false;
mEntry = ls;
mNick = nick;
mLocal = local;
mUnpublished = unpublished;
mIsUs = false;
mCountry = "";
}
public boolean isRouterInfo() {
return mIsRI;
}
// General methods
public Hash getHash() {
return mEntry.getHash();
}
// RouterInfo-specific methods
public boolean isUs() {
return mIsUs;
}
public int getCountryIcon() {
// http://daniel-codes.blogspot.com/2009/12/dynamically-retrieving-resources-in.html
try {
Class<R.drawable> res = R.drawable.class;
Field field = res.getField("flag_" + mCountry);
return field.getInt(null);
}
catch (Exception e) {
return 0;
}
}
// LeaseSet-specific methods
public String getNickname() {
return mNick;
}
public boolean isLocal() {
return mLocal;
}
public boolean isUnpublished() {
return mUnpublished;
}
}

View File

@ -0,0 +1,57 @@
package net.i2p.android.router.netdb;
import java.util.List;
import net.i2p.android.router.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class NetDbEntryAdapter extends ArrayAdapter<NetDbEntry> {
private final LayoutInflater mInflater;
public NetDbEntryAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<NetDbEntry> entries) {
clear();
if (entries != null) {
for (NetDbEntry entry : entries) {
add(entry);
}
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
NetDbEntry entry = getItem(position);
if (entry.isRouterInfo()) {
v = mInflater.inflate(R.layout.listitem_routerinfo, parent, false);
int countryIcon = entry.getCountryIcon();
if (countryIcon > 0) {
ImageView country = (ImageView) v.findViewById(R.id.ri_country);
country.setImageDrawable(getContext().getResources()
.getDrawable(countryIcon));
}
} else {
v = mInflater.inflate(R.layout.listitem_leaseset, parent, false);
TextView nickname = (TextView) v.findViewById(R.id.ls_nickname);
nickname.setText(entry.getNickname());
}
TextView hash = (TextView) v.findViewById(R.id.dbentry_hash);
hash.setText(entry.getHash().toBase64());
return v;
}
}

View File

@ -0,0 +1,150 @@
package net.i2p.android.router.netdb;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
private RouterContext mRContext;
private boolean mRouters;
private List<NetDbEntry> mData;
public NetDbEntryLoader(Context context, RouterContext rContext, boolean routers) {
super(context);
mRContext = rContext;
mRouters = routers;
}
private static class RouterInfoComparator implements Comparator<RouterInfo> {
public int compare(RouterInfo l, RouterInfo r) {
return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64());
}
}
private class LeaseSetComparator implements Comparator<LeaseSet> {
public int compare(LeaseSet l, LeaseSet r) {
Destination dl = l.getDestination();
Destination dr = r.getDestination();
boolean locall = mRContext.clientManager().isLocal(dl);
boolean localr = mRContext.clientManager().isLocal(dr);
if (locall && !localr) return -1;
if (localr && !locall) return 1;
return dl.calculateHash().toBase64().compareTo(dr.calculateHash().toBase64());
}
}
@Override
public List<NetDbEntry> loadInBackground() {
List<NetDbEntry> ret = new ArrayList<>();
if (mRContext.netDb().isInitialized()) {
if (mRouters) {
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
routers.addAll(mRContext.netDb().getRouters());
for (RouterInfo ri : routers) {
NetDbEntry entry = NetDbEntry.fromRouterInfo(mRContext, ri);
ret.add(entry);
}
} else {
Set<LeaseSet> leases = new TreeSet<>(new LeaseSetComparator());
leases.addAll(mRContext.netDb().getLeases());
for (LeaseSet ls : leases) {
NetDbEntry entry = NetDbEntry.fromLeaseSet(mRContext, ls);
ret.add(entry);
}
}
}
return ret;
}
@Override
public void deliverResult(List<NetDbEntry> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
if (data != null) {
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<NetDbEntry> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
}
@Override
public void onCanceled(List<NetDbEntry> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<NetDbEntry> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
}

View File

@ -0,0 +1,220 @@
package net.i2p.android.router.netdb;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.android.router.R;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import java.util.List;
public class NetDbListFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
LoaderManager.LoaderCallbacks<List<NetDbEntry>> {
public static final String SHOW_ROUTERS = "show_routers";
private static final int ROUTER_LOADER_ID = 1;
private static final int LEASESET_LOADER_ID = 2;
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
private boolean mOnActivityCreated;
private RouterContextProvider mRouterContextProvider;
private OnEntrySelectedListener mEntrySelectedCallback;
private NetDbEntryAdapter mAdapter;
private boolean mRouters;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
private boolean mActivateOnItemClick = false;
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(boolean isRouterInfo, Hash entryHash);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mRouterContextProvider = (RouterContextProvider) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterContextProvider");
}
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEntrySelectedListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState
.getInt(STATE_ACTIVATED_POSITION));
}
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(
mActivateOnItemClick ? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new NetDbEntryAdapter(getActivity());
mRouters = getArguments().getBoolean(SHOW_ROUTERS);
setListAdapter(mAdapter);
mOnActivityCreated = true;
if (getRouterContext() != null)
onRouterConnectionReady();
else
setEmptyText(getResources().getString(
R.string.router_not_running));
}
public void onRouterConnectionReady() {
setEmptyText(getResources().getString((mRouters ?
R.string.netdb_routers_empty :
R.string.netdb_leases_empty)));
setListShown(false);
getLoaderManager().initLoader(mRouters ? ROUTER_LOADER_ID
: LEASESET_LOADER_ID, null, this);
}
@Override
public void onListItemClick(ListView parent, View view, int pos, long id) {
super.onListItemClick(parent, view, pos, id);
NetDbEntry entry = mAdapter.getItem(pos);
mEntrySelectedCallback.onEntrySelected(
entry.isRouterInfo(), entry.getHash());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_netdb_list_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_refresh:
if (getRouterContext() != null) {
setListShown(false);
getLoaderManager().restartLoader(mRouters ? ROUTER_LOADER_ID
: LEASESET_LOADER_ID, null, this);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
mActivateOnItemClick = activateOnItemClick;
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
// Duplicated from I2PFragmentBase because this extends ListFragment
private RouterContext getRouterContext() {
return mRouterContextProvider.getRouterContext();
}
// I2PFragmentBase.RouterContextUser
public void onRouterBind() {
if (mOnActivityCreated)
onRouterConnectionReady();
}
// LoaderManager.LoaderCallbacks<List<NetDbEntry>>
public Loader<List<NetDbEntry>> onCreateLoader(int id, Bundle args) {
return new NetDbEntryLoader(getActivity(),
getRouterContext(), mRouters);
}
public void onLoadFinished(Loader<List<NetDbEntry>> loader,
List<NetDbEntry> data) {
if (loader.getId() == (mRouters ?
ROUTER_LOADER_ID : LEASESET_LOADER_ID)) {
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
public void onLoaderReset(Loader<List<NetDbEntry>> loader) {
if (loader.getId() == (mRouters ?
ROUTER_LOADER_ID : LEASESET_LOADER_ID)) {
mAdapter.setData(null);
}
}
}

View File

@ -0,0 +1,180 @@
package net.i2p.android.router.netdb;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.ObjectCounter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>>> {
private RouterContext mRContext;
private List<ObjectCounter<String>> mData;
public NetDbStatsLoader(Context context, RouterContext rContext) {
super(context);
mRContext = rContext;
}
private static class RouterInfoComparator implements Comparator<RouterInfo> {
public int compare(RouterInfo l, RouterInfo r) {
return l.getHash().toBase64().compareTo(r.getHash().toBase64());
}
}
@Override
public List<ObjectCounter<String>> loadInBackground() {
List<ObjectCounter<String>> ret = new ArrayList<>();
ObjectCounter<String> versions = new ObjectCounter<>();
ObjectCounter<String> countries = new ObjectCounter<>();
ObjectCounter<String> transports = new ObjectCounter<>();
if (mRContext != null && mRContext.netDb() != null && mRContext.netDb().isInitialized()) {
Hash us = mRContext.routerHash();
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
routers.addAll(mRContext.netDb().getRouters());
for (RouterInfo ri : routers) {
Hash key = ri.getHash();
if (!key.equals(us)) {
String routerVersion = ri.getOption("router.version");
if (routerVersion != null)
versions.increment(routerVersion);
// XXX Disabled, no GeoIP file
String country = null;//mRContext.commSystem().getCountry(key);
if(country != null)
countries.increment(country);
transports.increment(classifyTransports(ri));
}
}
}
ret.add(versions);
ret.add(countries);
ret.add(transports);
return ret;
}
private static final int SSU = 1;
private static final int SSUI = 2;
private static final int NTCP = 4;
private static final int IPV6 = 8;
private static final String[] TNAMES = { "Hidden or starting up", "SSU", "SSU with introducers", "",
"NTCP", "NTCP and SSU", "NTCP and SSU with introducers", "",
"", "IPv6 SSU", "IPv6 Only SSU, introducers", "IPv6 SSU, introducers",
"IPv6 NTCP", "IPv6 NTCP, SSU", "IPv6 Only NTCP, SSU, introducers", "IPv6 NTCP, SSU, introducers" };
/**
* what transport types
*/
private static String classifyTransports(RouterInfo info) {
int rv = 0;
for (RouterAddress addr : info.getAddresses()) {
String style = addr.getTransportStyle();
if (style.equals("NTCP")) {
rv |= NTCP;
} else if (style.equals("SSU")) {
if (addr.getOption("iport0") != null)
rv |= SSUI;
else
rv |= SSU;
}
String host = addr.getHost();
if (host != null && host.contains(":"))
rv |= IPV6;
}
return TNAMES[rv];
}
@Override
public void deliverResult(List<ObjectCounter<String>> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
if (data != null) {
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<ObjectCounter<String>> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
}
@Override
public void onCanceled(List<ObjectCounter<String>> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<ObjectCounter<String>> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
}

View File

@ -0,0 +1,146 @@
package net.i2p.android.router.netdb;
import java.util.List;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.util.ObjectCounter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>> {
private NetDbPagerAdapter mNetDbPagerAdapter;
ViewPager mViewPager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.parentfragment_viewpager, container, false);
return v;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Set up NetDbPagerAdapter containing the categories
mNetDbPagerAdapter = new NetDbPagerAdapter(getChildFragmentManager());
// Set up ViewPager for swiping between categories
mViewPager = (ViewPager) getActivity().findViewById(R.id.pager);
mViewPager.setAdapter(mNetDbPagerAdapter);
mViewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mViewPager.setCurrentItem(position);
}
});
}
@Override
public void onRouterConnectionReady() {
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onRouterConnectionNotReady() {
Util.d("Router not running or not bound to NetDbSummaryPagerFragment");
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_netdb_list_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_refresh:
if (getRouterContext() != null) {
Util.d("Refresh called, restarting Loader");
mNetDbPagerAdapter.setData(null);
mViewPager.invalidate();
getLoaderManager().restartLoader(0, null, this);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public class NetDbPagerAdapter extends FragmentStatePagerAdapter {
private List<ObjectCounter<String>> mData;
public NetDbPagerAdapter(FragmentManager fm) {
super(fm);
}
public void setData(List<ObjectCounter<String>> data) {
mData = data;
notifyDataSetChanged();
}
@Override
public Fragment getItem(int i) {
if (mData == null)
return null;
return NetDbSummaryTableFragment.newInstance(i, mData.get(i));
}
@Override
public int getCount() {
if (mData == null)
return 0;
else
return 3;
}
@Override
public CharSequence getPageTitle(int i) {
switch (i) {
case 1:
return "Countries";
case 2:
return "Transports";
default:
return "Versions";
}
}
}
// LoaderManager.LoaderCallbacks<List<ObjectCounter<String>>>
public Loader<List<ObjectCounter<String>>> onCreateLoader(int id, Bundle args) {
return new NetDbStatsLoader(getActivity(), getRouterContext());
}
public void onLoadFinished(Loader<List<ObjectCounter<String>>> loader,
List<ObjectCounter<String>> data) {
mNetDbPagerAdapter.setData(data);
}
public void onLoaderReset(Loader<List<ObjectCounter<String>>> loader) {
mNetDbPagerAdapter.setData(null);
}
}

View File

@ -0,0 +1,121 @@
package net.i2p.android.router.netdb;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.util.ObjectCounter;
import net.i2p.util.VersionComparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NetDbSummaryTableFragment extends Fragment {
private static final String CATEGORY = "category";
private static final String COUNTS = "counts";
private int mCategory;
private ObjectCounter<String> mCounts;
private TableLayout mTable;
public static NetDbSummaryTableFragment newInstance(int category,
ObjectCounter<String> counts) {
NetDbSummaryTableFragment f = new NetDbSummaryTableFragment();
Bundle args = new Bundle();
args.putInt(CATEGORY, category);
args.putSerializable(COUNTS, counts);
f.setArguments(args);
return f;
}
@SuppressWarnings("unchecked")
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_table, container, false);
mCategory = getArguments().getInt(CATEGORY);
mCounts = (ObjectCounter<String>) getArguments().getSerializable(COUNTS);
mTable = (TableLayout) v.findViewById(R.id.table);
List<String> objects = new ArrayList<>(mCounts.objects());
if (!objects.isEmpty()) {
createTableTitle();
switch (mCategory) {
case 1:
case 2:
Collections.sort(objects);
break;
default:
Collections.sort(objects,
Collections.reverseOrder(new VersionComparator()));
break;
}
for (String object : objects) {
int num = mCounts.count(object);
addTableRow(object, ""+num);
}
}
return v;
}
private void createTableTitle() {
TableRow titleRow;
TextView tl1, tl2;
titleRow = new TableRow(getActivity());
titleRow.setPadding(10, 0, 0, 0);
tl1 = new TextView(getActivity());
tl1.setTextSize(20);
tl2 = new TextView(getActivity());
tl2.setTextSize(20);
switch (mCategory) {
case 1:
tl1.setText("Transports");
break;
case 2:
tl1.setText("Country");
break;
default:
tl1.setText("Version");
break;
}
tl2.setText("Count");
titleRow.addView(tl1);
titleRow.addView(tl2);
mTable.addView(titleRow);
}
private void addTableRow(String name, String count) {
TableRow row;
TextView tl1, tl2;
row = new TableRow(getActivity());
row.setPadding(10, 0, 0, 0);
tl1 = new TextView(getActivity());
tl2 = new TextView(getActivity());
tl1.setText(name);
tl2.setText(count);
row.addView(tl1);
row.addView(tl2);
mTable.addView(row);
}
}

View File

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

View File

@ -9,8 +9,9 @@ import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.IBinder;
import net.i2p.android.router.binder.RouterBinder;
import net.i2p.android.router.service.RouterBinder;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
public class I2PReceiver extends BroadcastReceiver {
@ -33,7 +34,7 @@ public class I2PReceiver extends BroadcastReceiver {
intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
@SuppressWarnings("LeakingThisInConstructor")
Intent registerReceiver = context.registerReceiver(this, intents);
_wasConnected = Util.isConnected(context);
_wasConnected = Connectivity.isConnected(context);
}
public void onReceive(Context context, Intent intent) {
@ -57,7 +58,7 @@ public class I2PReceiver extends BroadcastReceiver {
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
action.equals(Intent.ACTION_TIME_TICK)) {
boolean connected = Util.isConnected(context);
boolean connected = Connectivity.isConnected(context);
if (_wasConnected && !connected) {
// notify + 2 timer ticks
if (++_unconnectedCount >= 3) {

View File

@ -0,0 +1,25 @@
package net.i2p.android.router.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import net.i2p.android.router.I2PConstants;
import net.i2p.android.router.service.RouterService;
public class OnBootReceiver extends BroadcastReceiver implements I2PConstants {
public static final String PREF_START_ON_BOOT = ANDROID_PREF_PREFIX + "startOnBoot";
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean startOnBoot = prefs.getBoolean(PREF_START_ON_BOOT, false);
if (startOnBoot) {
Intent routerService = new Intent(context, RouterService.class);
context.startService(routerService);
}
}
}

View File

@ -1,10 +1,12 @@
package net.i2p.android.router.service;
import android.content.Context;
import java.io.File;
import net.i2p.android.router.util.Util;
import net.i2p.util.FileUtil;
import java.io.File;
class Init {
private final Context ctx;
@ -32,9 +34,8 @@ class Init {
File tmp = new File(myDir, "tmp");
File[] files = tmp.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
Util.i("Deleting old file/dir " + f);
for (File f : files) {
Util.d("Deleting old file/dir " + f);
FileUtil.rmdir(f, false);
}
}

View File

@ -1,8 +1,12 @@
package net.i2p.android.router.service;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.BOB.BOB;
import net.i2p.addressbook.DaemonThread;
import net.i2p.android.apps.NewsFetcher;
import net.i2p.android.router.util.Notifications;
import net.i2p.android.router.util.Util;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.Job;
@ -31,28 +35,33 @@ import net.i2p.util.I2PAppThread;
*/
class LoadClientsJob extends JobImpl {
private Thread _fetcherThread;
private Notifications _notif;
private DaemonThread _addressbook;
private Thread _BOB;
private BOB _bob;
/** this is the delay to load (and start) the clients. */
private static final long LOAD_DELAY = 90*1000;
public LoadClientsJob(RouterContext ctx) {
public LoadClientsJob(RouterContext ctx, Notifications notif) {
super(ctx);
_notif = notif;
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
}
public String getName() { return "Start Clients"; };
public String getName() { return "Start Clients"; }
public void runJob() {
Job j = new RunI2PTunnel(getContext());
getContext().jobQueue().addJob(j);
NewsFetcher fetcher = NewsFetcher.getInstance(getContext());
_fetcherThread = new I2PAppThread(fetcher, "NewsFetcher", true);
_fetcherThread.start();
Thread t = new I2PAppThread(new StatSummarizer(), "StatSummarizer", true);
t.setPriority(Thread.NORM_PRIORITY - 1);
t.start();
NewsFetcher fetcher = NewsFetcher.getInstance(getContext(), _notif);
t = new I2PAppThread(fetcher, "NewsFetcher", true);
t.start();
_addressbook = new DaemonThread(new String[] {"addressbook"});
_addressbook.setName("Addressbook");
@ -60,19 +69,12 @@ class LoadClientsJob extends JobImpl {
_addressbook.start();
// add other clients here
Run_BOB bob = new Run_BOB();
_BOB = new I2PAppThread(bob, "BOB", true);
_BOB.start();
getContext().addShutdownTask(new ClientShutdownHook());
}
_bob = new BOB(I2PAppContext.getGlobalContext(), null, new String[0]);
try {
_bob.startup();
} catch (IOException ioe) {}
private class Run_BOB implements Runnable {
public void run() {
Util.i("BOB starting...");
BOB.main(null);
Util.i("BOB Stopped.");
_BOB = null;
}
getContext().addShutdownTask(new ClientShutdownHook());
}
private class RunI2PTunnel extends JobImpl {
@ -81,25 +83,25 @@ class LoadClientsJob extends JobImpl {
super(ctx);
}
public String getName() { return "Start I2P Tunnel"; };
public String getName() { return "Start I2P Tunnel"; }
public void runJob() {
Util.i("Starting i2ptunnel");
Util.d("Starting i2ptunnel");
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
int sz = tcg.getControllers().size();
Util.i("i2ptunnel started " + sz + " clients");
Util.d("i2ptunnel started " + sz + " clients");
}
}
private class ClientShutdownHook implements Runnable {
public void run() {
Util.i("client shutdown hook");
Util.d("client shutdown hook");
// i2ptunnel registers its own hook
if (_BOB != null)
BOB.stop();
if (_fetcherThread != null)
_fetcherThread.interrupt();
// StatSummarizer registers its own hook
// NewsFetcher registers its own hook
if (_bob != null)
_bob.shutdown(null);
if (_addressbook != null)
_addressbook.halt();
}

View File

@ -1,7 +1,6 @@
package net.i2p.android.router.binder;
package net.i2p.android.router.service;
import android.os.Binder;
import net.i2p.android.router.service.RouterService;
public class RouterBinder extends Binder {

View File

@ -1,66 +1,85 @@
package net.i2p.android.router.service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import net.i2p.android.router.binder.RouterBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.v4.content.LocalBroadcastManager;
import net.i2p.android.router.R;
import net.i2p.android.router.receiver.I2PReceiver;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Notifications;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.router.Job;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterLaunch;
import net.i2p.util.OrderedProperties;
import java.lang.ref.WeakReference;
import java.text.DecimalFormat;
/**
* Runs the router
*/
public class RouterService extends Service {
// These states persist even if we died... Yuck, it causes issues.
public enum State {
INIT, WAITING, STARTING, RUNNING,
// unplanned (router stopped itself), next: killSelf()
STOPPING, STOPPED,
// button, don't kill service when stopped, stay in MANUAL_STOPPED
MANUAL_STOPPING, MANUAL_STOPPED,
// button, DO kill service when stopped, next: killSelf()
MANUAL_QUITTING, MANUAL_QUITTED,
// Stopped by listener (no network), next: WAITING (spin waiting for network)
NETWORK_STOPPING, NETWORK_STOPPED
}
/**
* A request to this service for the current router state. Broadcasting
* this will trigger a state notification.
*/
public static final String LOCAL_BROADCAST_REQUEST_STATE = "net.i2p.android.LOCAL_BROADCAST_REQUEST_STATE";
/**
* A notification of the current state. This is informational; the state
* has not changed.
*/
public static final String LOCAL_BROADCAST_STATE_NOTIFICATION = "net.i2p.android.LOCAL_BROADCAST_STATE_NOTIFICATION";
/**
* The state has just changed.
*/
public static final String LOCAL_BROADCAST_STATE_CHANGED = "net.i2p.android.LOCAL_BROADCAST_STATE_CHANGED";
public static final String LOCAL_BROADCAST_EXTRA_STATE = "net.i2p.android.STATE";
private RouterContext _context;
private String _myDir;
//private String _apkPath;
private State _state = State.INIT;
private Thread _starterThread;
private StatusBar _statusBar;
private Notifications _notif;
private I2PReceiver _receiver;
private IBinder _binder;
private final Object _stateLock = new Object();
private Handler _handler;
private Runnable _updater;
private boolean mStartCalled;
private static final String SHARED_PREFS = "net.i2p.android.router";
private static final String LAST_STATE = "service.lastState";
private static final String EXTRA_RESTART = "restart";
private static final String MARKER = "************************************** ";
/**
* This is a list of callbacks that have been registered with the
* service. Note that this is package scoped (instead of private) so
* that it can be accessed more efficiently from inner classes.
*/
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
= new RemoteCallbackList<>();
@Override
public void onCreate() {
mStartCalled = false;
State lastState = getSavedState();
setState(State.INIT);
Util.i(this + " onCreate called"
Util.d(this + " onCreate called"
+ " Saved state is: " + lastState
+ " Current state is: " + _state);
@ -73,14 +92,17 @@ public class RouterService extends Service {
_statusBar = new StatusBar(this);
// Remove stale notification icon.
_statusBar.remove();
_notif = new Notifications(this);
_binder = new RouterBinder(this);
_handler = new Handler();
_updater = new Updater();
if(lastState == State.RUNNING) {
LocalBroadcastManager.getInstance(this).registerReceiver(onStateRequested,
new IntentFilter(LOCAL_BROADCAST_REQUEST_STATE));
if(lastState == State.RUNNING || lastState == State.ACTIVE) {
Intent intent = new Intent(this, RouterService.class);
intent.putExtra(EXTRA_RESTART, true);
onStartCommand(intent, 12345, 67890);
} else if(lastState == State.MANUAL_QUITTING) {
} else if(lastState == State.MANUAL_QUITTING || lastState == State.GRACEFUL_SHUTDOWN) {
synchronized(_stateLock) {
setState(State.MANUAL_QUITTED);
stopSelf(); // Die.
@ -88,19 +110,30 @@ public class RouterService extends Service {
}
}
private BroadcastReceiver onStateRequested = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Broadcast the current state within this app.
Intent ni = new Intent(LOCAL_BROADCAST_STATE_NOTIFICATION);
ni.putExtra(LOCAL_BROADCAST_EXTRA_STATE, (android.os.Parcelable) _state);
LocalBroadcastManager.getInstance(RouterService.this).sendBroadcast(ni);
}
};
/**
* NOT called by system if it restarts us after a crash
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Util.i(this + " onStart called"
Util.d(this + " onStart called"
+ " Intent is: " + intent
+ " Flags is: " + flags
+ " ID is: " + startId
+ " Current state is: " + _state);
mStartCalled = true;
boolean restart = intent != null && intent.getBooleanExtra(EXTRA_RESTART, false);
if(restart) {
Util.i(this + " RESTARTING");
Util.d(this + " RESTARTING");
}
synchronized(_stateLock) {
if(_state != State.INIT) //return START_STICKY;
@ -108,17 +141,17 @@ public class RouterService extends Service {
return START_NOT_STICKY;
}
_receiver = new I2PReceiver(this);
if(Util.isConnected(this)) {
if(Connectivity.isConnected(this)) {
if(restart) {
_statusBar.replace(StatusBar.ICON1, "I2P is restarting");
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting");
} else {
_statusBar.replace(StatusBar.ICON1, "I2P is starting up");
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
}
setState(State.STARTING);
_starterThread = new Thread(new Starter());
_starterThread.start();
} else {
_statusBar.replace(StatusBar.ICON6, "I2P is waiting for a network connection");
_statusBar.replace(StatusBar.ICON_WAITING_NETWORK, "I2P is waiting for a network connection");
setState(State.WAITING);
_handler.postDelayed(new Waiter(), 10 * 1000);
}
@ -139,15 +172,15 @@ public class RouterService extends Service {
private class Waiter implements Runnable {
public void run() {
Util.i(MARKER + this + " waiter handler"
Util.d(MARKER + this + " waiter handler"
+ " Current state is: " + _state);
if(_state == State.WAITING) {
if(Util.isConnected(RouterService.this)) {
if(Connectivity.isConnected(RouterService.this)) {
synchronized(_stateLock) {
if(_state != State.WAITING) {
return;
}
_statusBar.replace(StatusBar.ICON1, "Network connected, I2P is starting up");
_statusBar.replace(StatusBar.ICON_STARTING, "Network connected, I2P is starting up");
setState(State.STARTING);
_starterThread = new Thread(new Starter());
_starterThread.start();
@ -162,173 +195,32 @@ public class RouterService extends Service {
private class Starter implements Runnable {
public void run() {
Util.i(MARKER + this + " starter thread"
Util.d(MARKER + this + " starter thread"
+ " Current state is: " + _state);
//Util.i(MARKER + this + " JBigI speed test started");
//Util.d(MARKER + this + " JBigI speed test started");
//NativeBigInteger.main(null);
//Util.i(MARKER + this + " JBigI speed test finished, launching router");
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
// Before we launch, fix up any settings that need to be fixed here.
// This should be done in the core, but as of this writing it isn't!
// Step one. Load the propertites.
Properties props = new OrderedProperties();
Properties oldprops = new OrderedProperties();
String wrapName = _myDir + "/router.config";
try {
InputStream fin = new FileInputStream(new File(wrapName));
DataHelper.loadProps(props, fin);
} catch(IOException ioe) {
// shouldn't happen...
}
oldprops.putAll(props);
// Step two, check for any port settings, and copy for those that are missing.
int UDPinbound;
int UDPinlocal;
int TCPinbound;
int TCPinlocal;
UDPinbound = Integer.parseInt(props.getProperty("i2np.udp.port", "-1"));
UDPinlocal = Integer.parseInt(props.getProperty("i2np.udp.internalPort", "-1"));
TCPinbound = Integer.parseInt(props.getProperty("i2np.ntcp.port", "-1"));
TCPinlocal = Integer.parseInt(props.getProperty("i2np.ntcp.internalPort", "-1"));
boolean hasUDPinbound = UDPinbound != -1;
boolean hasUDPinlocal = UDPinlocal != -1;
boolean hasTCPinbound = TCPinbound != -1;
boolean hasTCPinlocal = TCPinlocal != -1;
// check and clear values based on these:
boolean udp = Boolean.parseBoolean(props.getProperty("i2np.udp.enable", "false"));
boolean tcp = Boolean.parseBoolean(props.getProperty("i2np.ntcp.enable", "false"));
// Fix if both are false.
if(!(udp || tcp)) {
// If both are not on, turn them both on.
props.setProperty("i2np.udp.enable", "true");
props.setProperty("i2np.ntcp.enable", "true");
}
// Fix if we have local but no inbound
if(!hasUDPinbound && hasUDPinlocal) {
// if we got a local port and no external port, set it
hasUDPinbound = true;
UDPinbound = UDPinlocal;
}
if(!hasTCPinbound && hasTCPinlocal) {
// if we got a local port and no external port, set it
hasTCPinbound = true;
TCPinbound = TCPinlocal;
}
boolean anyUDP = hasUDPinbound || hasUDPinlocal;
boolean anyTCP = hasTCPinbound || hasTCPinlocal;
boolean anyport = anyUDP || anyTCP;
if(!anyport) {
// generate one for UDPinbound, and fall thru.
// FIX ME: Possibly not the best but should be OK.
Random generator = new Random(System.currentTimeMillis());
UDPinbound = generator.nextInt(55500) + 10000;
anyUDP = true;
}
// Copy missing port numbers
if(anyUDP && !anyTCP) {
TCPinbound = UDPinbound;
TCPinlocal = UDPinlocal;
}
if(anyTCP && !anyUDP) {
UDPinbound = TCPinbound;
UDPinlocal = TCPinlocal;
}
// reset for a retest.
hasUDPinbound = UDPinbound != -1;
hasUDPinlocal = UDPinlocal != -1;
hasTCPinbound = TCPinbound != -1;
hasTCPinlocal = TCPinlocal != -1;
anyUDP = hasUDPinbound || hasUDPinlocal;
anyTCP = hasTCPinbound || hasTCPinlocal;
boolean checkAnyUDP = anyUDP && udp;
boolean checkAnyTCP = anyTCP && tcp;
// Enable things that need to be enabled.
// Disable anything that needs to be disabled.
if(!checkAnyUDP && !checkAnyTCP) {
// enable the one(s) with values.
if(anyUDP) {
udp = true;
}
if(anyTCP) {
tcp = true;
}
}
if(!udp) {
props.setProperty("i2np.udp.enable", "false");
props.remove("i2np.udp.port");
props.remove("i2np.udp.internalPort");
} else {
props.setProperty("i2np.udp.enable", "true");
if(hasUDPinbound) {
props.setProperty("i2np.udp.port", Integer.toString(UDPinbound));
} else {
props.remove("i2np.udp.port");
}
if(hasUDPinlocal) {
props.setProperty("i2np.udp.internalPort", Integer.toString(UDPinlocal));
} else {
props.remove("i2np.udp.internalPort");
}
}
if(!tcp) {
props.setProperty("i2np.ntcp.enable", "false");
props.remove("i2np.ntcp.port");
props.remove("i2np.ntcp.internalPort");
} else {
props.setProperty("i2np.ntcp.enable", "true");
if(hasTCPinbound) {
props.setProperty("i2np.ntcp.port", Integer.toString(TCPinbound));
} else {
props.remove("i2np.ntcp.port");
}
if(hasTCPinlocal) {
props.setProperty("i2np.ntcp.internalPort", Integer.toString(TCPinlocal));
} else {
props.remove("i2np.ntcp.internalPort");
}
}
// WHEW! Now test for any changes.
if(!props.equals(oldprops)) {
// save fixed properties.
try {
DataHelper.storeProps(props, new File(wrapName));
} catch(IOException ioe) {
// shouldn't happen...
}
}
// _NOW_ launch the router!
// Launch the router!
RouterLaunch.main(null);
synchronized(_stateLock) {
if(_state != State.STARTING) {
return;
}
setState(State.RUNNING);
List<?> contexts = RouterContext.listContexts();
if((contexts == null) || (contexts.isEmpty())) {
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
_context = Util.getRouterContext();
if (_context == null) {
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
}
_statusBar.replace(StatusBar.ICON2, "I2P is running");
_context = (RouterContext) contexts.get(0);
_context.router().setKillVMOnEnd(false);
Job loadJob = new LoadClientsJob(_context);
Job loadJob = new LoadClientsJob(_context, _notif);
_context.jobQueue().addJob(loadJob);
_context.addShutdownTask(new ShutdownHook());
_context.addFinalShutdownTask(new FinalShutdownHook());
_starterThread = null;
}
Util.i("Router.main finished");
Util.d("Router.main finished");
}
}
@ -336,7 +228,7 @@ public class RouterService extends Service {
public void run() {
RouterContext ctx = _context;
if(ctx != null && _state == State.RUNNING) {
if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN)) {
Router router = ctx.router();
if(router.isAlive()) {
updateStatus(ctx);
@ -345,6 +237,7 @@ public class RouterService extends Service {
_handler.postDelayed(this, 15 * 1000);
}
}
private String _currTitle;
private boolean _hadTunnels;
private void updateStatus(RouterContext ctx) {
@ -367,34 +260,88 @@ public class RouterService extends Service {
fmt = new DecimalFormat("#0.00");
}
String status =
"I2P "
+ active + '/' + known + " peers connected";
String text =
getResources().getString(R.string.notification_status_bw,
fmt.format(inBW), fmt.format(outBW));
String details =
fmt.format(inBW) + '/' + fmt.format(outBW) + " KBps"
+ "; Expl " + inEx + '/' + outEx
+ "; Client " + inCl + '/' + outCl;
String bigText =
getResources().getString(R.string.notification_status_bw,
fmt.format(inBW), fmt.format(outBW)) + '\n'
+ getResources().getString(R.string.notification_status_peers,
active, known) + '\n'
+ getResources().getString(R.string.notification_status_expl,
inEx, outEx) + '\n'
+ getResources().getString(R.string.notification_status_client,
inCl, outCl);
boolean haveTunnels = inCl > 0 && outCl > 0;
if(haveTunnels != _hadTunnels) {
if(haveTunnels) {
_statusBar.replace(StatusBar.ICON3, "Client tunnels are ready");
if (isGracefulShutdownInProgress()) {
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
_currTitle = "Stopping I2P in " + DataHelper.formatDuration(ms);
} else {
_statusBar.replace(StatusBar.ICON2, "Client tunnels are down");
_currTitle = "Stopping I2P";
}
} else if (haveTunnels != _hadTunnels) {
if(haveTunnels) {
_currTitle = "Client tunnels are ready";
setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle);
} else {
_currTitle = "Client tunnels are down";
setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, _currTitle);
}
_hadTunnels = haveTunnels;
}
_statusBar.update(status, details);
} else if (_currTitle == null || _currTitle.equals(""))
_currTitle = "I2P is running";
_statusBar.update(_currTitle, text, bigText);
}
@Override
public IBinder onBind(Intent intent) {
Util.i(this + "onBind called"
Util.d(this + "onBind called"
+ " Current state is: " + _state);
return _binder;
Util.d("Intent action: " + intent.getAction());
// Select the interface to return.
if (RouterBinder.class.getName().equals(intent.getAction())) {
// Local Activity wanting access to the RouterContext
Util.d("Returning RouterContext binder");
return _binder;
}
if (IRouterState.class.getName().equals(intent.getAction())) {
// Someone wants to monitor the router state.
Util.d("Returning state binder");
return mStatusBinder;
}
Util.d("Unknown binder request, returning null");
return null;
}
/**
* IRouterState is defined through IDL
*/
private final IRouterState.Stub mStatusBinder = new IRouterState.Stub() {
public void registerCallback(IRouterStateCallback cb)
throws RemoteException {
if (cb != null) mStateCallbacks.register(cb);
}
public void unregisterCallback(IRouterStateCallback cb)
throws RemoteException {
if (cb != null) mStateCallbacks.unregister(cb);
}
public boolean isStarted() throws RemoteException {
return mStartCalled;
}
public State getState() throws RemoteException {
return _state;
}
};
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
@ -402,7 +349,7 @@ public class RouterService extends Service {
// ******** following methods may be accessed from Activities and Receivers ************
/**
* @returns null if router is not running
* @return null if router is not running
*/
public RouterContext getRouterContext() {
RouterContext rv = _context;
@ -413,10 +360,12 @@ public class RouterService extends Service {
return null;
}
if(_state != State.RUNNING
&& _state != State.ACTIVE
&& _state != State.STOPPING
&& _state != State.MANUAL_STOPPING
&& _state != State.MANUAL_QUITTING
&& _state != State.NETWORK_STOPPING) {
&& _state != State.NETWORK_STOPPING
&& _state != State.GRACEFUL_SHUTDOWN) {
return null;
}
return rv;
@ -430,14 +379,18 @@ public class RouterService extends Service {
}
public boolean canManualStop() {
return _state == State.WAITING || _state == State.STARTING || _state == State.RUNNING;
return _state == State.WAITING || _state == State.STARTING ||
_state == State.RUNNING || _state == State.ACTIVE ||
_state == State.GRACEFUL_SHUTDOWN;
}
/**
* Stop and don't restart the router, but keep the service
*
* Apparently unused - see manualQuit()
*/
public void manualStop() {
Util.i("manualStop called"
Util.d("manualStop called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(!canManualStop()) {
@ -446,8 +399,9 @@ public class RouterService extends Service {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING) {
_statusBar.replace(StatusBar.ICON4, "Stopping I2P");
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
stopperThread.start();
}
@ -458,7 +412,7 @@ public class RouterService extends Service {
* Stop the router and kill the service
*/
public void manualQuit() {
Util.i("manualQuit called"
Util.d("manualQuit called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(!canManualStop()) {
@ -467,8 +421,9 @@ public class RouterService extends Service {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING) {
_statusBar.replace(StatusBar.ICON4, "Stopping I2P");
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
Thread stopperThread = new Thread(new Stopper(State.MANUAL_QUITTING, State.MANUAL_QUITTED));
stopperThread.start();
} else if(_state == State.WAITING) {
@ -482,14 +437,15 @@ public class RouterService extends Service {
* Stop and then spin waiting for a network connection, then restart
*/
public void networkStop() {
Util.i("networkStop called"
Util.d("networkStop called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING) {
_statusBar.replace(StatusBar.ICON4, "Network disconnected, stopping I2P");
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Network disconnected, stopping I2P");
// don't change state, let the shutdown hook do it
Thread stopperThread = new Thread(new Stopper(State.NETWORK_STOPPING, State.NETWORK_STOPPING));
stopperThread.start();
@ -503,32 +459,156 @@ public class RouterService extends Service {
}
public void manualStart() {
Util.i("restart called"
Util.d("restart called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(!canManualStart()) {
return;
}
_statusBar.replace(StatusBar.ICON1, "I2P is starting up");
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is starting up");
setState(State.STARTING);
_starterThread = new Thread(new Starter());
_starterThread.start();
}
}
/**
* Graceful Shutdown
*
* @since 0.9.19
*/
public boolean isGracefulShutdownInProgress() {
if (_state == State.GRACEFUL_SHUTDOWN) {
RouterContext ctx = _context;
return ctx != null && ctx.router().gracefulShutdownInProgress();
}
return false;
}
private String _oldTitle;
/**
* Graceful Shutdown
*
* @since 0.9.19
*/
public void gracefulShutdown() {
Util.d("gracefulShutdown called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(!canManualStop()) {
return;
}
if(_state == State.STARTING || _state == State.WAITING) {
manualQuit();
return;
}
if(_state == State.RUNNING || _state == State.ACTIVE) {
RouterContext ctx = _context;
if(ctx != null && ctx.router().isAlive()) {
int part = ctx.tunnelManager().getParticipatingCount();
if(part <= 0) {
manualQuit();
} else {
ctx.router().shutdownGracefully();
_oldTitle = _currTitle;
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P in " + DataHelper.formatDuration(ms));
} else {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
}
setState(State.GRACEFUL_SHUTDOWN);
}
}
}
}
}
/**
* Cancel Graceful Shutdown
*
* @since 0.9.19
*/
public void cancelGracefulShutdown() {
Util.d("cancelGracefulShutdown called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(_state != State.GRACEFUL_SHUTDOWN) {
return;
}
RouterContext ctx = _context;
if(ctx != null && ctx.router().isAlive()) {
ctx.router().cancelGracefulShutdown();
_currTitle = _oldTitle;
if (_hadTunnels) {
setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, "Shutdown cancelled");
} else {
setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, "Shutdown cancelled");
}
}
}
}
// ******** end methods accessed from Activities and Receivers ************
private static final int STATE_MSG = 1;
/**
* Our Handler used to execute operations on the main thread.
*/
private final Handler mHandler = new StateHandler(new WeakReference<>(this));
private static class StateHandler extends Handler {
WeakReference<RouterService> mReference;
public StateHandler(WeakReference<RouterService> reference) {
mReference = reference;
}
@Override
public void handleMessage(Message msg) {
RouterService parent = mReference.get();
if (parent == null)
return;
switch (msg.what) {
case STATE_MSG:
final State state = parent._state;
// Broadcast to all clients the new state.
final int N = parent.mStateCallbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
parent.mStateCallbacks.getBroadcastItem(i).stateChanged(state);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
parent.mStateCallbacks.finishBroadcast();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Turn off the status bar. Unregister the receiver. If we were running,
* fire up the Stopper thread.
*/
@Override
public void onDestroy() {
Util.i("onDestroy called"
Util.d("onDestroy called"
+ " Current state is: " + _state);
_handler.removeCallbacks(_updater);
_statusBar.remove();
LocalBroadcastManager.getInstance(this).unregisterReceiver(onStateRequested);
I2PReceiver rcvr = _receiver;
if(rcvr != null) {
synchronized(rcvr) {
@ -545,9 +625,10 @@ public class RouterService extends Service {
if(_state == State.STARTING) {
_starterThread.interrupt();
}
if(_state == State.STARTING || _state == State.RUNNING) {
if(_state == State.STARTING || _state == State.RUNNING ||
_state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN) {
// should this be in a thread?
_statusBar.replace(StatusBar.ICON5, "I2P is shutting down");
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
Thread stopperThread = new Thread(new Stopper(State.STOPPING, State.STOPPED));
stopperThread.start();
}
@ -574,14 +655,14 @@ public class RouterService extends Service {
public void run() {
try {
Util.i(MARKER + this + " stopper thread"
Util.d(MARKER + this + " stopper thread"
+ " Current state is: " + _state);
RouterContext ctx = _context;
if(ctx != null) {
ctx.router().shutdown(Router.EXIT_HARD);
}
_statusBar.remove();
Util.i("********** Router shutdown complete");
Util.d("********** Router shutdown complete");
synchronized(_stateLock) {
if(_state == nextState) {
setState(stopState);
@ -600,9 +681,9 @@ public class RouterService extends Service {
private class ShutdownHook implements Runnable {
public void run() {
Util.i(this + " shutdown hook"
Util.d(this + " shutdown hook"
+ " Current state is: " + _state);
_statusBar.replace(StatusBar.ICON5, "I2P is shutting down");
_statusBar.replace(StatusBar.ICON_SHUTTING_DOWN, "I2P is shutting down");
I2PReceiver rcvr = _receiver;
if(rcvr != null) {
synchronized(rcvr) {
@ -622,7 +703,8 @@ public class RouterService extends Service {
_starterThread.interrupt();
}
if(_state == State.WAITING || _state == State.STARTING
|| _state == State.RUNNING) {
|| _state == State.RUNNING || _state == State.ACTIVE
|| _state == State.GRACEFUL_SHUTDOWN) {
setState(State.STOPPING);
}
}
@ -639,7 +721,7 @@ public class RouterService extends Service {
public void run() {
try {
Util.i(this + " final shutdown hook"
Util.d(this + " final shutdown hook"
+ " Current state is: " + _state);
//I2PReceiver rcvr = _receiver;
@ -657,13 +739,17 @@ public class RouterService extends Service {
setState(State.WAITING);
_handler.postDelayed(new Waiter(), 10 * 1000);
} else if(_state == State.STARTING || _state == State.RUNNING
|| _state == State.STOPPING) {
Util.i(this + " died of unknown causes");
|| _state == State.ACTIVE || _state == State.STOPPING) {
Util.w(this + " died of unknown causes");
setState(State.STOPPED);
// Unregister all callbacks.
mStateCallbacks.kill();
stopForeground(true);
stopSelf();
} else if(_state == State.MANUAL_QUITTING) {
} else if(_state == State.MANUAL_QUITTING || _state == State.GRACEFUL_SHUTDOWN) {
setState(State.MANUAL_QUITTED);
// Unregister all callbacks.
mStateCallbacks.kill();
stopForeground(true);
stopSelf();
}
@ -677,17 +763,24 @@ public class RouterService extends Service {
private State getSavedState() {
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
String stateString = prefs.getString(LAST_STATE, State.INIT.toString());
for(State s : State.values()) {
if(s.toString().equals(stateString)) {
return s;
}
try {
return State.valueOf(stateString);
} catch (IllegalArgumentException e) {
return State.INIT;
}
return State.INIT;
}
private void setState(State s) {
_state = s;
saveState();
// Broadcast the new state within this app.
Intent intent = new Intent(LOCAL_BROADCAST_STATE_CHANGED);
intent.putExtra(LOCAL_BROADCAST_EXTRA_STATE, (android.os.Parcelable) _state);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
// Notify other apps that the state has changed
mHandler.sendEmptyMessage(STATE_MSG);
}
/**

View File

@ -0,0 +1,145 @@
package net.i2p.android.router.service;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
public class StatSummarizer implements Runnable {
private final RouterContext _context;
private final List<SummaryListener> _listeners;
// TODO remove static instance
private static StatSummarizer _instance;
private volatile boolean _isRunning = true;
private Thread _thread;
public StatSummarizer() {
_context = Util.getRouterContext();
_listeners = new CopyOnWriteArrayList<>();
_instance = this;
if (_context != null)
_context.addShutdownTask(new Shutdown());
}
public static StatSummarizer instance() { return _instance; }
public void run() {
// We can't do anything without a RouterContext
if (_context == null)
return;
_thread = Thread.currentThread();
String specs = "";
while (_isRunning && _context.router().isAlive()) {
specs = adjustDatabases(specs);
try { Thread.sleep(60 * 1000);} catch (InterruptedException ie) {}
}
}
/** list of SummaryListener instances */
public List<SummaryListener> getListeners() { return _listeners; }
public SummaryListener getListener(String rateName, long period) {
for (SummaryListener lsnr : _listeners) {
if (lsnr.getName().equals(rateName + "." + period))
return lsnr;
}
return null;
}
private static final String DEFAULT_DATABASES =
"bw.sendRate.60000"
+ ",bw.recvRate.60000"
+ ",router.memoryUsed.60000"
+ ",router.activePeers.60000";
private String adjustDatabases(String oldSpecs) {
String spec = _context.getProperty("stat.summaries", DEFAULT_DATABASES);
if ( ( (spec == null) && (oldSpecs == null) ) ||
( (spec != null) && (oldSpecs != null) && (oldSpecs.equals(spec))) )
return oldSpecs;
List<Rate> old = parseSpecs(oldSpecs);
List<Rate> newSpecs = parseSpecs(spec);
// remove old ones
for (Rate r : old) {
if (!newSpecs.contains(r))
removeDb(r);
}
// add new ones
StringBuilder buf = new StringBuilder();
boolean comma = false;
for (Rate r : newSpecs) {
if (!old.contains(r))
addDb(r);
if (comma)
buf.append(',');
else
comma = true;
buf.append(r.getRateStat().getName()).append(".").append(r.getPeriod());
}
return buf.toString();
}
private void removeDb(Rate r) {
for (SummaryListener lsnr : _listeners) {
if (lsnr.getRate().equals(r)) {
// no iter.remove() in COWAL
_listeners.remove(lsnr);
lsnr.stopListening();
return;
}
}
}
private void addDb(Rate r) {
SummaryListener lsnr = new SummaryListener(r);
lsnr.startListening();
_listeners.add(lsnr);
}
/**
* @param specs statName.period,statName.period,statName.period
* @return list of Rate objects
*/
List<Rate> parseSpecs(String specs) {
StringTokenizer tok = new StringTokenizer(specs, ",");
List<Rate> rv = new ArrayList<>();
while (tok.hasMoreTokens()) {
String spec = tok.nextToken();
int split = spec.lastIndexOf('.');
if ( (split <= 0) || (split + 1 >= spec.length()) )
continue;
String name = spec.substring(0, split);
String per = spec.substring(split+1);
long period;
try {
period = Long.parseLong(per);
RateStat rs = _context.statManager().getRate(name);
if (rs != null) {
Rate r = rs.getRate(period);
if (r != null)
rv.add(r);
}
} catch (NumberFormatException nfe) {}
}
return rv;
}
private class Shutdown implements Runnable {
public void run() {
_isRunning = false;
if (_thread != null)
_thread.interrupt();
for (SummaryListener lsnr : _listeners) {
lsnr.stopListening();
}
_listeners.clear();
}
}
}

View File

@ -0,0 +1,108 @@
package net.i2p.android.router.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import net.i2p.android.router.MainActivity;
import net.i2p.android.router.R;
class StatusBar {
private final Context ctx;
private final NotificationManager mNotificationManager;
private final NotificationCompat.Builder mNotifyBuilder;
private Notification mNotif;
private static final int ID = 1337;
public static final int ICON_STARTING = R.drawable.ic_stat_router_starting;
public static final int ICON_RUNNING = R.drawable.ic_stat_router_running;
public static final int ICON_ACTIVE = R.drawable.ic_stat_router_active;
public static final int ICON_STOPPING = R.drawable.ic_stat_router_stopping;
public static final int ICON_SHUTTING_DOWN = R.drawable.ic_stat_router_shutting_down;
public static final int ICON_WAITING_NETWORK = R.drawable.ic_stat_router_waiting_network;
StatusBar(Context cx) {
ctx = cx;
mNotificationManager = (NotificationManager) ctx.getSystemService(
Context.NOTIFICATION_SERVICE);
Thread.currentThread().setUncaughtExceptionHandler(
new CrashHandler(mNotificationManager));
int icon = ICON_STARTING;
// won't be shown if replace() is called
String text = "Starting I2P";
mNotifyBuilder = new NotificationCompat.Builder(ctx)
.setContentText(text)
.setSmallIcon(icon)
.setOngoing(true)
.setOnlyAlertOnce(true);
Intent intent = new Intent(ctx, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(pi);
}
public void replace(int icon, String text) {
mNotifyBuilder.setSmallIcon(icon)
.setStyle(null)
.setTicker(text);
update(text);
}
public void update(String text) {
String title = "I2P Status";
update(title, text);
}
public void update(String title, String text, String bigText) {
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle()
.bigText(bigText));
update(title, text);
}
public void update(String title, String text) {
mNotifyBuilder.setContentTitle(title)
.setContentText(text);
mNotif = mNotifyBuilder.build();
mNotificationManager.notify(ID, mNotif);
}
public void remove() {
mNotificationManager.cancel(ID);
}
/**
* http://stackoverflow.com/questions/4028742/how-to-clear-a-notification-if-activity-crashes
*/
private static class CrashHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler defaultUEH;
private final NotificationManager mgr;
public CrashHandler(NotificationManager nMgr) {
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
mgr = nMgr;
}
public void uncaughtException(Thread t, Throwable e) {
if (mgr != null) {
try {
mgr.cancel(ID);
} catch (Throwable ex) {}
}
System.err.println("In CrashHandler " + e);
e.printStackTrace(System.err);
defaultUEH.uncaughtException(t, e);
}
}
public Notification getNote() {
return mNotif;
}
}

View File

@ -0,0 +1,80 @@
package net.i2p.android.router.service;
import java.util.Observable;
import java.util.Observer;
import com.androidplot.xy.SimpleXYSeries;
import com.androidplot.xy.XYSeries;
import net.i2p.I2PAppContext;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.stat.RateSummaryListener;
public class SummaryListener implements RateSummaryListener {
public static final int HISTORY_SIZE = 30;
private final I2PAppContext _context;
private final Rate _rate;
private String _name;
private SimpleXYSeries _series;
private MyObservable _notifier;
public SummaryListener(Rate r) {
_context = I2PAppContext.getGlobalContext();
_rate = r;
_notifier = new MyObservable();
}
// encapsulates management of the observers watching this rate for update events:
class MyObservable extends Observable {
@Override
public void notifyObservers() {
setChanged();
super.notifyObservers();
}
}
public void addObserver(Observer observer) {
_notifier.addObserver(observer);
}
public void removeObserver(Observer observer) {
_notifier.deleteObserver(observer);
}
public void add(double totalValue, long eventCount, double totalEventTime,
long period) {
long now = now();
long when = now / 1000;
double val = eventCount > 0 ? (totalValue / eventCount) : 0d;
if (_series.size() > HISTORY_SIZE)
_series.removeFirst();
_series.addLast(when, val);
_notifier.notifyObservers();
}
public Rate getRate() { return _rate; }
public String getName() { return _name; }
public XYSeries getSeries() { return _series; }
long now() { return _context.clock().now(); }
public void startListening() {
RateStat rs = _rate.getRateStat();
long period = _rate.getPeriod();
_name = rs.getName() + "." + period;
_series = new SimpleXYSeries(_name);
_series.useImplicitXVals();
_rate.setSummaryListener(this);
}
public void stopListening() {
_rate.setSummaryListener(null);
}
}

View File

@ -0,0 +1,38 @@
package net.i2p.android.router.stats;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.android.router.service.RouterService;
import android.os.Bundle;
public class PeersActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Start with the base view
if (savedInstanceState == null) {
PeersFragment f = new PeersFragment();
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}
}
/**
* Not bound by the time onResume() is called, so we have to do it here.
* If it is bound we update twice.
*/
@Override
protected void onRouterBind(RouterService svc) {
PeersFragment f = (PeersFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
f.update();
}
@Override
public void onBackPressed() {
PeersFragment f = (PeersFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (!f.onBackPressed())
super.onBackPressed();
}
}

View File

@ -1,17 +1,22 @@
package net.i2p.android.router.activity;
package net.i2p.android.router.stats;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import java.io.IOException;
import java.io.StringWriter;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.util.Util;
import net.i2p.android.router.web.I2PWebViewClient;
import net.i2p.router.CommSystemFacade;
public class PeersActivity extends I2PActivityBase {
public class PeersFragment extends I2PFragmentBase {
private I2PWebViewClient _wvClient;
@ -20,17 +25,24 @@ public class PeersActivity extends I2PActivityBase {
private static final String FOOTER = "</body></html>";
@Override
public void onCreate(Bundle savedInstanceState)
{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.peers);
WebView wv = (WebView) findViewById(R.id.peers_webview);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.peers, container, false);
WebView wv = (WebView) v.findViewById(R.id.peers_webview);
wv.getSettings().setLoadsImagesAutomatically(true); // was false
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
wv.getSettings().setUseWideViewPort(true);
_wvClient = new I2PWebViewClient(this);
_wvClient = new I2PWebViewClient();
wv.setWebViewClient(_wvClient);
wv.getSettings().setBuiltInZoomControls(true);
return v;
}
@Override
@ -39,17 +51,8 @@ public class PeersActivity extends I2PActivityBase {
update();
}
/**
* Not bound by the time onResume() is called, so we have to do it here.
* If it is bound we update twice.
*/
@Override
protected void onRouterBind(RouterService svc) {
update();
}
private void update() {
WebView wv = (WebView) findViewById(R.id.peers_webview);
public void update() {
WebView wv = (WebView) getActivity().findViewById(R.id.peers_webview);
wv.clearHistory(); // fixes having to hit back.
CommSystemFacade comm = getCommSystem();
String data;
@ -76,26 +79,27 @@ public class PeersActivity extends I2PActivityBase {
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
WebView wv = (WebView) findViewById(R.id.peers_webview);
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
_wvClient.cancelAll();
wv.stopLoading();
public boolean onBackPressed() {
WebView wv = (WebView) getActivity().findViewById(R.id.peers_webview);
_wvClient.cancelAll();
wv.stopLoading();
// We do not want to go back, or keep history... Theere is no need to.
// What we DO want to do is exit!
//if (wv.canGoBack()) {
// wv.goBack();
// return true;
//}
}
return super.onKeyDown(keyCode, event);
// We do not want to go back, or keep history... There is no need to.
// What we DO want to do is exit!
//if (wv.canGoBack()) {
// wv.goBack();
// return true;
//}
return false;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_web_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
WebView wv = (WebView) findViewById(R.id.peers_webview);
switch (item.getItemId()) {
case R.id.menu_reload:
update();

View File

@ -0,0 +1,170 @@
package net.i2p.android.router.stats;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener;
import net.i2p.stat.Rate;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
public class RateGraphActivity extends I2PActivityBase {
private static final String SELECTED_RATE = "selected_rate";
private String[] mRates;
private long[] mPeriods;
private Spinner mSpinner;
private boolean mFinishOnResume;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (StatSummarizer.instance() != null) {
// Get the rates currently being graphed
List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
TreeSet<SummaryListener> ordered = new TreeSet<>(new AlphaComparator());
ordered.addAll(listeners);
if (ordered.size() > 0) {
// Extract the rates and periods
mRates = new String[ordered.size()];
mPeriods = new long[ordered.size()];
int i = 0;
for (SummaryListener listener : ordered) {
Rate r = listener.getRate();
mRates[i] = r.getRateStat().getName();
mPeriods[i] = r.getPeriod();
i++;
}
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
mSpinner.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, mRates));
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selectRate(i);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
if (savedInstanceState != null) {
int selected = savedInstanceState.getInt(SELECTED_RATE);
mSpinner.setSelection(selected);
} else
selectRate(0);
} else {
DialogFragment df = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.no_graphs_configured)
.setPositiveButton(R.string.configure_graphs, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mFinishOnResume = true;
Intent i = new Intent(RateGraphActivity.this, SettingsActivity.class);
// Navigation to a sub-category doesn't seem to work yet
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
i.setAction(SettingsActivity.ACTION_PREFS_GRAPHS);
} else {
i.putExtra("settings", "graphs");
}
startActivity(i);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
finish();
}
})
.setCancelable(false);
return builder.create();
}
};
df.show(getSupportFragmentManager(), "nographs");
}
} else {
DialogFragment df = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.graphs_not_ready)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
})
.setCancelable(false);
return builder.create();
}
};
df.show(getSupportFragmentManager(), "graphsnotready");
}
}
private void selectRate(int position) {
String rateName = mRates[position];
long period = mPeriods[position];
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f, rateName).commit();
}
@Override
public void onResume() {
super.onResume();
if (mFinishOnResume) {
finish();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mSpinner != null)
outState.putInt(SELECTED_RATE, mSpinner.getSelectedItemPosition());
}
private static class AlphaComparator implements Comparator<SummaryListener> {
public int compare(SummaryListener l, SummaryListener r) {
String lName = l.getRate().getRateStat().getName();
String rName = r.getRate().getRateStat().getName();
int rv = lName.compareTo(rName);
if (rv != 0)
return rv;
return (int) (l.getRate().getPeriod() - r.getRate().getPeriod());
}
}
}

View File

@ -0,0 +1,163 @@
package net.i2p.android.router.stats;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.androidplot.Plot;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeries;
import com.androidplot.xy.XYStepMode;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener;
import net.i2p.android.router.util.Util;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Observable;
import java.util.Observer;
public class RateGraphFragment extends I2PFragmentBase {
// redraws a plot whenever an update is received:
private class MyPlotUpdater implements Observer {
Plot plot;
public MyPlotUpdater(Plot plot) {
this.plot = plot;
}
public void update(Observable o, Object arg) {
Util.d("Redrawing plot");
plot.redraw();
}
}
public static final String RATE_NAME = "rate_name";
public static final String RATE_PERIOD = "rate_period";
private Handler _handler;
private SetupTask _setupTask;
private SummaryListener _listener;
private XYPlot _ratePlot;
private MyPlotUpdater _plotUpdater;
public static RateGraphFragment newInstance(String name, long period) {
RateGraphFragment f = new RateGraphFragment();
Bundle args = new Bundle();
args.putString(RATE_NAME, name);
args.putLong(RATE_PERIOD, period);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_handler = new Handler();
_setupTask = new SetupTask();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_graph, container, false);
_ratePlot = (XYPlot) v.findViewById(R.id.rate_stat_plot);
return v;
}
@Override
public void onStart() {
super.onStart();
_handler.removeCallbacks(_setupTask);
_handler.postDelayed(_setupTask, 100);
}
@Override
public void onStop() {
super.onStop();
if (_listener != null && _plotUpdater != null) {
Util.d("Removing plot updater from listener");
_listener.removeObserver(_plotUpdater);
}
_handler.removeCallbacks(_setupTask);
}
private class SetupTask implements Runnable {
public void run() {
String rateName = getArguments().getString(RATE_NAME);
long period = getArguments().getLong(RATE_PERIOD);
Util.d("Setting up " + rateName + "." + period);
if (StatSummarizer.instance() == null) {
Util.d("StatSummarizer is null, delaying setup");
_handler.postDelayed(this, 1000);
return;
}
_listener = StatSummarizer.instance().getListener(rateName, period);
if (_listener == null) {
Util.d("Listener is null, delaying setup");
_handler.postDelayed(this, 1000);
return;
}
XYSeries rateSeries = _listener.getSeries();
_plotUpdater = new MyPlotUpdater(_ratePlot);
_ratePlot.addSeries(rateSeries, new LineAndPointFormatter(Color.rgb(0, 0, 0), null, Color.rgb(0, 80, 0), null));
Util.d("Adding plot updater to listener");
_listener.addObserver(_plotUpdater);
_ratePlot.setDomainStepMode(XYStepMode.SUBDIVIDE);
_ratePlot.setDomainStepValue(SummaryListener.HISTORY_SIZE);
// thin out domain/range tick labels so they dont overlap each other:
_ratePlot.setTicksPerDomainLabel(5);
_ratePlot.setTicksPerRangeLabel(3);
_ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
_ratePlot.setRangeValueFormat(new Format() {
@Override
public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
@NonNull FieldPosition pos) {
double val = ((Number) obj).doubleValue();
if (val >= 10 * 1000 * 1000)
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
else if (val >= 8 * 100 * 1000)
return new DecimalFormat("0.0 M").format(val / (1000 * 1000), toAppendTo, pos);
else if (val >= 10 * 1000)
return new DecimalFormat("0 k").format(val / (1000), toAppendTo, pos);
else if (val >= 8 * 100)
return new DecimalFormat("0.0 k").format(val / (1000), toAppendTo, pos);
else
return new DecimalFormat("0").format(val, toAppendTo, pos);
}
@Override
public Object parseObject(String source, @NonNull ParsePosition pos) {
return null;
}
});
Util.d("Redrawing plot");
_ratePlot.redraw();
}
}
}

View File

@ -4,6 +4,9 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import net.i2p.android.router.provider.CacheProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -14,7 +17,6 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.i2p.android.router.provider.CacheProvider;
/**
* A least recently used cache with a max number of entries
@ -32,7 +34,7 @@ public class AppCache {
/** the LRU cache */
private final Map<Integer, Object> _cache;
private static final Integer DUMMY = Integer.valueOf(0);
private static final Integer DUMMY = 0;
private static final String DIR_NAME = "appCache";
/** fragment into this many subdirectories */
private static final int NUM_DIRS = 32;
@ -89,7 +91,7 @@ public class AppCache {
public Uri addCacheFile(Uri key, boolean setAsCurrentBase) {
int hash = toHash(key);
synchronized(_cache) {
_cache.put(Integer.valueOf(hash), DUMMY);
_cache.put(hash, DUMMY);
}
// file:/// uri
//return Uri.fromFile(toFile(hash)).toString();
@ -104,7 +106,7 @@ public class AppCache {
public void removeCacheFile(Uri key) {
int hash = toHash(key);
synchronized(_cache) {
_cache.remove(Integer.valueOf(hash));
_cache.remove(hash);
}
deleteContent(key);
}
@ -121,7 +123,7 @@ public class AppCache {
// poke the LRU
Object present;
synchronized(_cache) {
present = _cache.get(Integer.valueOf(hash));
present = _cache.get(hash);
}
if (present != null)
setAsCurrentBase(key);
@ -173,17 +175,16 @@ public class AppCache {
File[] files = dir.listFiles();
if (files == null)
return 0;
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (f.isDirectory()) {
rv += enumerate(f, fileList);
} else {
long len = f.length();
if (len > 0) {
fileList.add(f);
rv += len;
for (File f : files) {
if (f.isDirectory()) {
rv += enumerate(f, fileList);
} else {
long len = f.length();
if (len > 0) {
fileList.add(f);
rv += len;
} else {
f.delete();
f.delete();
}
}
}
@ -195,7 +196,7 @@ public class AppCache {
try {
int hash = toHash(f);
synchronized(_cache) {
_cache.put(Integer.valueOf(hash), DUMMY);
_cache.put(hash, DUMMY);
}
} catch (IllegalArgumentException iae) {
Util.d("Huh bad file?" + iae);
@ -294,7 +295,7 @@ public class AppCache {
@Override
public Object put(Integer key, Object value) {
Object rv = super.put(key, value);
File f = toFile(key.intValue());
File f = toFile(key);
if (f.exists()) {
_totalSize += f.length();
}
@ -306,7 +307,7 @@ public class AppCache {
public Object remove(Object key) {
Object rv = super.remove(key);
if ( /* rv != null && */ key instanceof Integer) {
File f = toFile(((Integer)key).intValue());
File f = toFile((Integer) key);
if (f.exists()) {
_totalSize -= f.length();
f.delete();

View File

@ -0,0 +1,125 @@
package net.i2p.android.router.util;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public abstract class BetterAsyncTaskLoader<T> extends AsyncTaskLoader<T> {
protected T mData;
public BetterAsyncTaskLoader(Context context) {
super(context);
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@Override
public void deliverResult(T data) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (data != null) {
releaseResources(data);
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
T oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
// Start watching for changes
onStartMonitoring();
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
/**
* Handles a request to completely reset the Loader.
*/
@Override
protected void onReset() {
super.onReset();
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
// Stop monitoring for changes.
onStopMonitoring();
}
/**
* Handles a request to cancel a load.
*/
@Override
public void onCanceled(T data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
protected abstract void onStartMonitoring();
protected abstract void onStopMonitoring();
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
* For a simple List, there is nothing to do. For something like a Cursor, we
* would close it in this method. All resources associated with the Loader
* should be released here.
*/
protected abstract void releaseResources(T data);
}

View File

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

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