Compare commits

...

493 Commits

Author SHA1 Message Date
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
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
2340500083 Updated Irc2P servers 2014-02-24 01:14:11 +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
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
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
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
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
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
472 changed files with 130934 additions and 4056 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

@ -3,8 +3,15 @@ host = https://www.transifex.com
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, zh_CN: zh
[I2P.android]
file_filter = 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="19" />
<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>

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.0.2
- Android Support Repository
- Gradle 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,116 +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 22.3 and platform-tools version 19)
=====================
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.4 (API 19)
# download at least that one. Otherwise you must change the
# target in project.properties from android-19 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").
# update the compatibility project
../android-sdk-linux/tools/android update lib-project -p ../android-sdk-linux/extras/android/support/v7/appcompat -t android-19
# 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.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
# 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

45
TODO Normal file
View File

@ -0,0 +1,45 @@
# 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
# Short-term
- Disable uPnP when on cell networks
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
- Display release notes directly on new router version
- 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?
- I2PTunnel
- Show all messages somewhere
- Improve detail page, expose advanced settings
- Add edit page
# Long-term
- Remote router support
- Implement a "router wrapper" that can represent a local or remote router
- Implement/use client APIs to talk to remote router
- I2CP
- I2PControl

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

183
app/build.gradle Normal file
View File

@ -0,0 +1,183 @@
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 4745223
versionName '0.9.16-rc1'
minSdkVersion 9
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
}
signingConfigs {
release
}
buildTypes {
release {
signingConfig signingConfigs.release
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
lintOptions {
abortOnError false
}
productFlavors {
free {
applicationId 'net.i2p.android'
}
donate {
applicationId 'net.i2p.android.donate'
}
legacy {
applicationId 'net.i2p.android.router'
}
}
}
dependencies {
compile project(':routerjars')
compile project(':client')
compile 'com.android.support:support-v4:21.0.2'
compile 'com.android.support:appcompat-v7:21.0.2'
compile 'com.android.support:recyclerview-v7:21.0.2'
compile 'com.android.support:cardview-v7:21.0.2'
compile 'net.i2p.android.ext:floatingactionbutton:1.1.0'
compile files('libs/androidplot-core-0.6.1.jar')
}
dependencyVerification {
verify = [
'com.android.support:support-v4:126a4c291f41f75f3fff4968e9d397bc8454cdff4d8f994cbe0524e3bad76e72',
'com.android.support:appcompat-v7:b760fd3d0b0b0547a1bcef9031b40939f31049ba955f04c8fdc5aa09a25d19e9',
'com.android.support:recyclerview-v7:71ef0f5659b3019dc33c5ffb346ea01df1f66735506f38d43fd783fbcb0370ce',
'com.android.support:cardview-v7:cb4d7ee9ebb6edffa7203eff0d207b4e88425599a8ed37d94b650bc84390c4eb',
'net.i2p.android.ext:floatingactionbutton:84cf5b67a66337bef59b46f57468c730387ca766b5a5f06ca852ba46cabbc0fb',
]
}
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 copyDrawableResources(type: Copy) {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
into 'src/main/res/drawable'
}
task certificatesZip(type: Zip) {
archiveName = 'certificates_zip'
from files('' + i2pbase + '/installer/resources/certificates')
}
task copyRawResources(type:Copy) {
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
into 'src/main/res/raw'
}
task copyI2PResources
copyI2PResources.dependsOn copyDrawableResources
copyI2PResources.dependsOn copyRawResources
// For peers WebView
task copyConsoleImagesAssets(type: Copy) {
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 'src/main/assets/themes/console/images'
}
task copyConsoleLightAssets(type: Copy) {
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
into 'src/main/assets/themes/console/light'
}
task copyConsoleLightImagesAssets(type: Copy) {
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
into 'src/main/assets/themes/console/light/images'
}
task copyI2PAssets
copyI2PAssets.dependsOn copyConsoleImagesAssets
copyI2PAssets.dependsOn copyConsoleLightAssets
copyI2PAssets.dependsOn copyConsoleLightImagesAssets
preBuild.dependsOn copyI2PResources
preBuild.dependsOn 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,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,206 @@
<?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" />
<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="net.i2p.android" />
<activity
android:name=".MainActivity"
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="net.i2p.android.router.START_I2P" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".NewsActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P News"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.help.HelpActivity"
android:label="Help"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.help.BrowserConfigActivity"
android: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="I2P License Information"
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="I2P Settings"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookSettingsActivity"
android:label="Addressbook Settings"
android:launchMode="singleTop"
android:parentActivityName=".addressbook.AddressbookActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookActivity"
android:label="Addressbook"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
</activity>
<activity
android:name=".addressbook.AddressbookAddWizardActivity"
android:label="Add new Destination"
android:parentActivityName=".addressbook.AddressbookActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
android:label="I2PTunnel"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
android:label="I2PTunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
android:label="Tunnel Creation Wizard"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
</activity>
<activity
android:name=".log.LogActivity"
android:label="I2P Logs"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".log.LogDetailActivity"
android:label="Log Entry"
android:parentActivityName=".log.LogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.log.LogActivity" />
</activity>
<activity
android:name=".stats.RateGraphActivity"
android:label="Rate Graph"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".stats.PeersActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P Peers and Transport Status"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".netdb.NetDbActivity"
android:label="NetDB"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".netdb.NetDbDetailActivity"
android:label="NetDB Detail"
android:parentActivityName=".netdb.NetDbActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.netdb.NetDbActivity" />
</activity>
</application>
</manifest>

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,82 @@
package net.i2p.android.help;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
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(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<Browser>();
Map<String, String> recommendedMap = new HashMap<String, String>();
for (int i = 0; i < recommended.size(); i++) {
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
}
Map<String, String> supportedMap = new HashMap<String, String>();
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,125 @@
package net.i2p.android.help;
import android.content.Intent;
import android.os.Bundle;
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;
@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();
}
int category = getIntent().getIntExtra(CATEGORY, -1);
if (category >= 0)
showCategory(category);
}
@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:
onBackPressed();
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);
}
}
// HelpListFragment.OnEntrySelectedListener
@Override
public void onEntrySelected(int entry) {
if (entry == CAT_CONFIGURE_BROWSER) {
Intent i = new Intent(this, BrowserConfigActivity.class);
startActivity(i);
} else
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 net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import android.os.Bundle;
public class TunnelDetailActivity extends I2PActivityBase implements
TunnelDetailFragment.OnTunnelDeletedListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
if (savedInstanceState == null) {
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, detailFrag).commit();
}
}
// TunnelDetailFragment.OnTunnelDeletedListener
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft) {
finish();
}
}

View File

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

View File

@ -0,0 +1,273 @@
package net.i2p.android.i2ptunnel;
import java.util.List;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.Toast;
import net.i2p.android.i2ptunnel.util.TunnelConfig;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
public class TunnelEntry {
public static final int RUNNING = 1;
public static final int STARTING = 2;
public static final int NOT_RUNNING = 3;
public static final int STANDBY = 4;
private final Context mContext;
private final TunnelController mController;
private final int mId;
public static TunnelEntry createNewTunnel(
Context ctx,
TunnelControllerGroup tcg,
TunnelConfig cfg) {
int tunnelId = tcg.getControllers().size();
List<String> msgs = TunnelUtil.saveTunnel(
ctx, tcg, -1, cfg.getConfig());
// TODO: Do something else with the other messages.
Toast.makeText(ctx.getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
return new TunnelEntry(ctx, cur, tunnelId);
}
public TunnelEntry(Context context, TunnelController controller, int id) {
mContext = context;
mController = controller;
mId = id;
}
public int getId() {
return mId;
}
public TunnelController getController() {
return mController;
}
/* General tunnel data for any type */
public String getName() {
if (mController.getName() != null)
return mController.getName();
else
return mContext.getResources()
.getString(R.string.i2ptunnel_new_tunnel);
}
public String getInternalType() {
return mController.getType();
}
public String getType() {
return TunnelUtil.getTypeName(mController.getType(), mContext);
}
public String getDescription() {
String rv = mController.getDescription();
if (rv != null)
return rv;
return "";
}
public boolean startAutomatically() {
return mController.getStartOnLoad();
}
public int getStatus() {
if (mController.getIsRunning()) {
if (isClient() && mController.getIsStandby())
return STANDBY;
else
return RUNNING;
} else if (mController.getIsStarting()) return STARTING;
else return NOT_RUNNING;
}
public boolean 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 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,71 @@
package net.i2p.android.i2ptunnel;
import android.content.Context;
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)));
getContext().startActivity(i);
}
});
}
return v;
}
}

View File

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

View File

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

View File

@ -0,0 +1,297 @@
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.TunnelConfig;
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.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 = TunnelConfig.createFromWizard(getActivity(), mGroup, tunnelData);
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), mGroup, cfg);
mAdapter.add(tunnel);
}
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
mActivateOnItemClick = activateOnItemClick;
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
// Duplicated from I2PFragmentBase because this extends ListFragment
private RouterContext getRouterContext() {
return mRouterContextProvider.getRouterContext();
}
// I2PFragmentBase.RouterContextUser
public void onRouterBind() {
if (mOnActivityCreated)
onRouterConnectionReady();
}
// LoaderManager.LoaderCallbacks<List<TunnelEntry>>
public Loader<List<TunnelEntry>> onCreateLoader(int id, Bundle args) {
return new TunnelEntryLoader(getActivity(), mGroup, mClientTunnels);
}
public void onLoadFinished(Loader<List<TunnelEntry>> loader,
List<TunnelEntry> data) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(data);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
public void onLoaderReset(Loader<List<TunnelEntry>> loader) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(null);
}
}
}

View File

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

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

View File

@ -0,0 +1,241 @@
package net.i2p.android.i2ptunnel.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import android.content.Context;
import android.content.res.Resources;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.FileUtil;
import net.i2p.util.SecureFile;
public abstract class TunnelUtil {
public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) {
if (tunnel < 0) return null;
if (tcg == null) return null;
List<TunnelController> controllers = tcg.getControllers();
if (controllers.size() > tunnel)
return controllers.get(tunnel);
else
return null;
}
public static List<String> saveTunnel(Context ctx,
TunnelControllerGroup tcg,
int tunnelId,
Properties config) {
// Get current tunnel controller
TunnelController cur = getController(tcg, tunnelId);
if (config == null) {
List<String> ret = new ArrayList<String>();
ret.add("Invalid params");
return ret;
}
if (cur == null) {
// creating new
cur = new TunnelController(config, "", true);
tcg.addController(cur);
if (cur.getStartOnLoad())
cur.startTunnelBackground();
} else {
cur.setConfig(config, "");
}
// Only modify other shared tunnels
// if the current tunnel is shared, and of supported type
if (Boolean.parseBoolean(cur.getSharedClient()) && isClient(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same I2CP options
List<TunnelController> controllers = tcg.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = controllers.get(i);
// Current tunnel modified by user, skip
if (c == cur) continue;
// Only modify this non-current tunnel
// if it belongs to a shared destination, and is of supported type
if (Boolean.parseBoolean(c.getSharedClient()) && isClient(c.getType())) {
Properties cOpt = c.getConfig("");
if (config.getProperty("option.inbound.quantity") != null)
cOpt.setProperty("option.inbound.quantity", config.getProperty("option.inbound.quantity"));
if (config.getProperty("option.outbound.quantity") != null)
cOpt.setProperty("option.outbound.quantity", config.getProperty("option.outbound.quantity"));
if (config.getProperty("option.inbound.length") != null)
cOpt.setProperty("option.inbound.length", config.getProperty("option.inbound.length"));
if (config.getProperty("option.outbound.length") != null)
cOpt.setProperty("option.outbound.length", config.getProperty("option.outbound.length"));
if (config.getProperty("option.inbound.lengthVariance") != null)
cOpt.setProperty("option.inbound.lengthVariance", config.getProperty("option.inbound.lengthVariance"));
if (config.getProperty("option.outbound.lengthVariance") != null)
cOpt.setProperty("option.outbound.lengthVariance", config.getProperty("option.outbound.lengthVariance"));
if (config.getProperty("option.inbound.backupQuantity") != null)
cOpt.setProperty("option.inbound.backupQuantity", config.getProperty("option.inbound.backupQuantity"));
if (config.getProperty("option.outbound.backupQuantity") != null)
cOpt.setProperty("option.outbound.backupQuantity", config.getProperty("option.outbound.backupQuantity"));
cOpt.setProperty("option.inbound.nickname", TunnelConfig.CLIENT_NICKNAME);
cOpt.setProperty("option.outbound.nickname", TunnelConfig.CLIENT_NICKNAME);
c.setConfig(cOpt, "");
}
}
}
return doSave(ctx, tcg);
}
/**
* Stop the tunnel, delete from config,
* rename the private key file if in the default directory
*/
public static List<String> deleteTunnel(Context ctx, TunnelControllerGroup tcg, int tunnelId) {
List<String> msgs;
TunnelController cur = getController(tcg, tunnelId);
if (cur == null) {
msgs = new ArrayList<String>();
msgs.add("Invalid tunnel number");
return msgs;
}
msgs = tcg.removeController(cur);
msgs.addAll(doSave(ctx, tcg));
// Rename private key file if it was a default name in
// the default directory, so it doesn't get reused when a new
// tunnel is created.
// Use configured file name if available, not the one from the form.
String pk = cur.getPrivKeyFile();
//if (pk == null)
// pk = _privKeyFile;
if (pk != null && pk.startsWith("i2ptunnel") && pk.endsWith("-privKeys.dat") &&
((!isClient(cur.getType())) || cur.getPersistentClientKey())) {
I2PAppContext context = I2PAppContext.getGlobalContext();
File pkf = new File(context.getConfigDir(), pk);
if (pkf.exists()) {
String name = cur.getName();
if (name == null) {
name = cur.getDescription();
if (name == null) {
name = cur.getType();
if (name == null)
name = Long.toString(context.clock().now());
}
}
name = "i2ptunnel-deleted-" + name.replace(' ', '_') + '-' + context.clock().now() + "-privkeys.dat";
File backupDir = new SecureFile(context.getConfigDir(), TunnelController.KEY_BACKUP_DIR);
File to;
if (backupDir.isDirectory() || backupDir.mkdir())
to = new File(backupDir, name);
else
to = new File(context.getConfigDir(), name);
boolean success = FileUtil.rename(pkf, to);
if (success)
msgs.add("Private key file " + pkf.getAbsolutePath() +
" renamed to " + to.getAbsolutePath());
}
}
return msgs;
}
private static List<String> doSave(Context ctx, TunnelControllerGroup tcg) {
List<String> rv = tcg.clearAllMessages();
try {
tcg.saveConfig();
rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_saved));
} catch (IOException ioe) {
Util.e("Failed to save config file", ioe);
rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_save_failed) + ": " + ioe.toString());
}
return rv;
}
/* General tunnel data for any type */
public static String getTypeFromName(String typeName, Context ctx) {
Resources res = ctx.getResources();
if (res.getString(R.string.i2ptunnel_type_client).equals(typeName))
return "client";
else if (res.getString(R.string.i2ptunnel_type_httpclient).equals(typeName))
return "httpclient";
else if (res.getString(R.string.i2ptunnel_type_ircclient).equals(typeName))
return "ircclient";
else if (res.getString(R.string.i2ptunnel_type_server).equals(typeName))
return "server";
else if (res.getString(R.string.i2ptunnel_type_httpserver).equals(typeName))
return "httpserver";
else if (res.getString(R.string.i2ptunnel_type_sockstunnel).equals(typeName))
return "sockstunnel";
else if (res.getString(R.string.i2ptunnel_type_socksirctunnel).equals(typeName))
return "socksirctunnel";
else if (res.getString(R.string.i2ptunnel_type_connectclient).equals(typeName))
return "connectclient";
else if (res.getString(R.string.i2ptunnel_type_ircserver).equals(typeName))
return "ircserver";
else if (res.getString(R.string.i2ptunnel_type_streamrclient).equals(typeName))
return "streamrclient";
else if (res.getString(R.string.i2ptunnel_type_streamrserver).equals(typeName))
return "streamrserver";
else if (res.getString(R.string.i2ptunnel_type_httpbidirserver).equals(typeName))
return "httpbidirserver";
else
return typeName;
}
public static String getTypeName(String type, Context context) {
Resources res = context.getResources();
if ("client".equals(type))
return res.getString(R.string.i2ptunnel_type_client);
else if ("httpclient".equals(type))
return res.getString(R.string.i2ptunnel_type_httpclient);
else if ("ircclient".equals(type))
return res.getString(R.string.i2ptunnel_type_ircclient);
else if ("server".equals(type))
return res.getString(R.string.i2ptunnel_type_server);
else if ("httpserver".equals(type))
return res.getString(R.string.i2ptunnel_type_httpserver);
else if ("sockstunnel".equals(type))
return res.getString(R.string.i2ptunnel_type_sockstunnel);
else if ("socksirctunnel".equals(type))
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
else if ("connectclient".equals(type))
return res.getString(R.string.i2ptunnel_type_connectclient);
else if ("ircserver".equals(type))
return res.getString(R.string.i2ptunnel_type_ircserver);
else if ("streamrclient".equals(type))
return res.getString(R.string.i2ptunnel_type_streamrclient);
else if ("streamrserver".equals(type))
return res.getString(R.string.i2ptunnel_type_streamrserver);
else if ("httpbidirserver".equals(type))
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
else
return type;
}
public static boolean isClient(String type) {
return ( ("client".equals(type)) ||
("httpclient".equals(type)) ||
("sockstunnel".equals(type)) ||
("socksirctunnel".equals(type)) ||
("connectclient".equals(type)) ||
("streamrclient".equals(type)) ||
("ircclient".equals(type)));
}
public static String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) {
TunnelController tun = getController(tcg, tunnel);
if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile();
if (tunnel < 0)
tunnel = tcg == null ? 999 : tcg.getControllers().size();
return "i2ptunnel" + tunnel + "-privKeys.dat";
}
}

View File

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

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);
@ -121,7 +126,7 @@ class InitActivities {
if ( allcertificates != null) {
for (int i = 0; i < allcertificates.length; i++) {
File f = allcertificates[i];
Util.i("Deleting old certificate file/dir " + f);
Util.d("Deleting old certificate file/dir " + f);
FileUtil.rmdir(f, false);
}
}
@ -137,6 +142,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 +158,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 +177,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 +199,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 +226,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);
}
/**
@ -262,7 +249,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 +259,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,19 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import android.os.Bundle;
public class LicenseActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Start with the base view
if (savedInstanceState == null) {
LicenseFragment f = new LicenseFragment();
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}
}
}

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

View File

@ -0,0 +1,531 @@
package net.i2p.android.router;
import android.app.Activity;
import android.graphics.Color;
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.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.dialog.VersionDialog;
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();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (RouterControlListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterControlListener");
}
}
/**
* Called when the fragment is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Init stuff here so settings work.
if(savedInstanceState != null) {
String saved = savedInstanceState.getString("status");
if(saved != null) {
_savedStatus = saved;
}
}
_keep = true;
_handler = new Handler();
_updater = new Updater();
_oneShotUpdate = new OneShotUpdate();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, container, false);
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights);
lightImage.setImageResource(R.drawable.routerlogo_0);
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
b.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
boolean on = ((ToggleButton) view).isChecked();
if (on) {
_startPressed = true;
mCallback.onStartRouterClicked();
updateOneShot();
checkFirstStart();
} else {
if(mCallback.onStopRouterClicked()) {
updateOneShot();
}
}
return true;
}
});
return v;
}
@Override
public void onStart() {
super.onStart();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
if(_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
tv.setText(_savedStatus);
}
checkDialog();
_handler.postDelayed(_updater, 100);
}
@Override
public void onStop() {
super.onStop();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
}
@Override
public void onResume() {
super.onResume();
updateOneShot();
}
@Override
public void onSaveInstanceState(Bundle outState) {
if(_savedStatus != null) {
outState.putString("status", _savedStatus);
}
super.onSaveInstanceState(outState);
}
private void updateOneShot() {
_handler.postDelayed(_oneShotUpdate, 10);
}
private class OneShotUpdate implements Runnable {
public void run() {
updateVisibility();
updateStatus();
}
}
private class Updater implements Runnable {
private int counter;
private final int delay = 1000;
private final int toloop = delay / 500;
public void run() {
updateVisibility();
if(counter++ % toloop == 0) {
updateStatus();
}
//_handler.postDelayed(this, 2500);
_handler.postDelayed(this, delay);
}
}
private void updateVisibility() {
boolean showOnOff = mCallback.shouldShowOnOff();
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
b.setVisibility(showOnOff ? View.VISIBLE : View.INVISIBLE);
boolean isOn = mCallback.shouldBeOn();
b.setChecked(isOn);
if (showOnOff && !isOn) {
// Sometimes the final state message from the RouterService
// is not received. Ensure that the state image is correct.
// TODO: Fix the race between RouterService shutdown and
// IRouterState unbinding.
updateState(State.INIT);
}
}
public boolean onBackPressed() {
RouterContext ctx = getRouterContext();
// RouterService svc = _routerService; Which is better to use?!
_keep = 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.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,19 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import android.os.Bundle;
public class NewsActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Start with the base view
if (savedInstanceState == null) {
NewsFragment f = new NewsFragment();
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}
}
}

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,261 @@
package net.i2p.android.router;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.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.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.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) {
if (ACTION_PREFS_NET.equals(action)) {
addPreferencesFromResource(R.xml.settings_net);
} else if (ACTION_PREFS_GRAPHS.equals(action)){
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(this, getPreferenceScreen(), Util.getRouterContext());
} else if (ACTION_PREFS_LOGGING.equals(action)) {
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
} else if (ACTION_PREFS_ADVANCED.equals(action)) {
addPreferencesFromResource(R.xml.settings_advanced);
}
} else 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 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);
*/
}
}
@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 logSettings = lProps.get(1);
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props);
// Apply new config if we are running.
RouterContext rCtx = Util.getRouterContext();
if (rCtx != null) {
rCtx.router().saveConfig(props, null);
// Merge in new log settings
saveLoggingChanges(rCtx, logSettings);
} else {
// Merge in new config settings, write the file.
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props);
// 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");
if ("net".equals(settings)) {
addPreferencesFromResource(R.xml.settings_net);
} else if ("graphs".equals(settings)) {
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
} else if ("logging".equals(settings)) {
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
} else if ("advanced".equals(settings)) {
addPreferencesFromResource(R.xml.settings_advanced);
}
}
}
@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,134 @@
package net.i2p.android.router.addressbook;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.android.router.util.Util;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
private RouterContext mRContext;
private String mBook;
private String mFilter;
private List<AddressEntry> mData;
public AddressEntryLoader(Context context, RouterContext rContext,
String book, String filter) {
super(context);
mRContext = rContext;
mBook = book;
mFilter = filter;
}
@Override
public List<AddressEntry> loadInBackground() {
// get the names
NamingService ns = NamingServiceUtil.getNamingService(mRContext, mBook);
Util.d("NamingService: " + ns.getName());
// After router shutdown we get nothing... why?
List<AddressEntry> ret = new ArrayList<AddressEntry>();
Map<String, Destination> names = new TreeMap<String, Destination>();
Properties searchProps = new Properties();
// Needed for HostsTxtNamingService
searchProps.setProperty("file", mBook);
if (mFilter != null && mFilter.length() > 0)
searchProps.setProperty("search", mFilter);
names.putAll(ns.getEntries(searchProps));
for (String hostName : names.keySet())
ret.add(new AddressEntry(hostName, names.get(hostName)));
return ret;
}
@Override
public void deliverResult(List<AddressEntry> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
if (data != null) {
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<AddressEntry> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
@Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
}
@Override
public void onCanceled(List<AddressEntry> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<AddressEntry> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
}

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

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,279 @@
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);
// Hide until needed
if (getRouterContext() == null) {
mSearchAddressbook.setVisible(false);
mAddToAddressbook.setVisibility(View.GONE);
}
// Only allow adding to private book
if (!PRIVATE_BOOK.equals(mBook)) {
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(),
getRouterContext(), mBook, mCurFilter);
}
public void onLoadFinished(Loader<List<AddressEntry>> loader,
List<AddressEntry> data) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
mAdapter.setData(data);
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
if (mCurFilter != null)
v.setText(getActivity().getResources().getString(
R.string.addressbook_search_header,
data.size()));
else
v.setText("");
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
public void onLoaderReset(Loader<List<AddressEntry>> loader) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
mAdapter.setData(null);
}
}
}

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,49 @@
package net.i2p.android.router.dialog;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import net.i2p.android.router.util.Util;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
public class AboutDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
View view = li.inflate(R.layout.fragment_dialog_about, null);
final String currentVersion = Util.getOurVersion(getActivity());
TextView tv = (TextView)view.findViewById(R.id.about_version);
tv.setText(currentVersion);
tv = (TextView)view.findViewById(R.id.url_project);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_android_bugs);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_android_volunteer);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
tv = (TextView)view.findViewById(R.id.url_donate);
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.menu_about)
.setView(view)
.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,42 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
public class ConfigureBrowserDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_browser_title)
.setMessage(R.string.configure_browser_for_i2p)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
});
return b.create();
}
}

View File

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

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) {

View File

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

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

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

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

View File

@ -45,7 +45,7 @@ public class CacheProvider extends ContentProvider {
//private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt()));
private static final String NONCE = "0";
private static final String SCHEME = "content";
public static final String AUTHORITY = "net.i2p.android.router";
public static final String AUTHORITY = "net.i2p.android";
/** includes the nonce */
public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE);

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

@ -34,7 +34,7 @@ class Init {
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
Util.i("Deleting old file/dir " + f);
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

@ -5,6 +5,10 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@ -13,8 +17,11 @@ import java.text.DecimalFormat;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import net.i2p.android.router.binder.RouterBinder;
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;
@ -28,39 +35,38 @@ import net.i2p.util.OrderedProperties;
*/
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
}
private RouterContext _context;
private String _myDir;
//private String _apkPath;
private State _state = State.INIT;
private Thread _starterThread;
private StatusBar _statusBar;
private Notifications _notif;
private I2PReceiver _receiver;
private IBinder _binder;
private final Object _stateLock = new Object();
private Handler _handler;
private Runnable _updater;
private boolean mStartCalled;
private static final String SHARED_PREFS = "net.i2p.android.router";
private static final String LAST_STATE = "service.lastState";
private static final String EXTRA_RESTART = "restart";
private static final String MARKER = "************************************** ";
/**
* This is a list of callbacks that have been registered with the
* service. Note that this is package scoped (instead of private) so
* that it can be accessed more efficiently from inner classes.
*/
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
= new RemoteCallbackList<IRouterStateCallback>();
@Override
public void onCreate() {
mStartCalled = false;
State lastState = getSavedState();
setState(State.INIT);
Util.i(this + " onCreate called"
Util.d(this + " onCreate called"
+ " Saved state is: " + lastState
+ " Current state is: " + _state);
@ -73,10 +79,11 @@ 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) {
if(lastState == State.RUNNING || lastState == State.ACTIVE) {
Intent intent = new Intent(this, RouterService.class);
intent.putExtra(EXTRA_RESTART, true);
onStartCommand(intent, 12345, 67890);
@ -93,14 +100,15 @@ public class RouterService extends Service {
*/
@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 +116,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 +147,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,11 +170,11 @@ 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.
@ -315,20 +323,19 @@ public class RouterService extends Service {
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 +343,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)) {
Router router = ctx.router();
if(router.isAlive()) {
updateStatus(ctx);
@ -345,6 +352,7 @@ public class RouterService extends Service {
_handler.postDelayed(this, 15 * 1000);
}
}
private String _currTitle;
private boolean _hadTunnels;
private void updateStatus(RouterContext ctx) {
@ -367,34 +375,81 @@ 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");
_currTitle = "Client tunnels are ready";
setState(State.ACTIVE);
_statusBar.replace(StatusBar.ICON_ACTIVE, _currTitle);
} else {
_statusBar.replace(StatusBar.ICON2, "Client tunnels are down");
_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);
@ -413,6 +468,7 @@ 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
@ -430,14 +486,14 @@ 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;
}
/**
* Stop and don't restart the router, but keep the service
*/
public void manualStop() {
Util.i("manualStop called"
Util.d("manualStop called"
+ " Current state is: " + _state);
synchronized(_stateLock) {
if(!canManualStop()) {
@ -446,8 +502,8 @@ 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) {
_statusBar.replace(StatusBar.ICON_STOPPING, "Stopping I2P");
Thread stopperThread = new Thread(new Stopper(State.MANUAL_STOPPING, State.MANUAL_STOPPED));
stopperThread.start();
}
@ -458,7 +514,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 +523,8 @@ 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) {
_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 +538,14 @@ 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) {
_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,13 +559,13 @@ 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();
@ -517,13 +573,43 @@ public class RouterService extends Service {
}
// ******** end methods accessed from Activities and Receivers ************
private static final int STATE_MSG = 1;
/**
* Our Handler used to execute operations on the main thread.
*/
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case STATE_MSG:
final State state = _state;
// Broadcast to all clients the new state.
final int N = mStateCallbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
mStateCallbacks.getBroadcastItem(i).stateChanged(state);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mStateCallbacks.finishBroadcast();
break;
default:
super.handleMessage(msg);
}
}
};
/**
* Turn off the status bar. Unregister the receiver. If we were running,
* fire up the Stopper thread.
*/
@Override
public void onDestroy() {
Util.i("onDestroy called"
Util.d("onDestroy called"
+ " Current state is: " + _state);
_handler.removeCallbacks(_updater);
@ -545,9 +631,9 @@ 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) {
// 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 +660,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 +686,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 +708,7 @@ public class RouterService extends Service {
_starterThread.interrupt();
}
if(_state == State.WAITING || _state == State.STARTING
|| _state == State.RUNNING) {
|| _state == State.RUNNING || _state == State.ACTIVE) {
setState(State.STOPPING);
}
}
@ -639,7 +725,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 +743,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) {
setState(State.MANUAL_QUITTED);
// Unregister all callbacks.
mStateCallbacks.kill();
stopForeground(true);
stopSelf();
}
@ -677,17 +767,17 @@ 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();
mHandler.sendEmptyMessage(STATE_MSG);
}
/**

View File

@ -0,0 +1,145 @@
package net.i2p.android.router.service;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
public class StatSummarizer implements Runnable {
private final RouterContext _context;
private final List<SummaryListener> _listeners;
// TODO remove static instance
private static StatSummarizer _instance;
private volatile boolean _isRunning = true;
private Thread _thread;
public StatSummarizer() {
_context = Util.getRouterContext();
_listeners = new CopyOnWriteArrayList<SummaryListener>();
_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<Rate>();
while (tok.hasMoreTokens()) {
String spec = tok.nextToken();
int split = spec.lastIndexOf('.');
if ( (split <= 0) || (split + 1 >= spec.length()) )
continue;
String name = spec.substring(0, split);
String per = spec.substring(split+1);
long period = -1;
try {
period = Long.parseLong(per);
RateStat rs = _context.statManager().getRate(name);
if (rs != null) {
Rate r = rs.getRate(period);
if (r != null)
rv.add(r);
}
} catch (NumberFormatException nfe) {}
}
return rv;
}
private class Shutdown implements Runnable {
public void run() {
_isRunning = false;
if (_thread != null)
_thread.interrupt();
for (SummaryListener lsnr : _listeners) {
lsnr.stopListening();
}
_listeners.clear();
}
}
}

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,167 @@
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.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<SummaryListener>(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<String>(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() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.no_graphs_configured)
.setPositiveButton(R.string.configure_graphs, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mFinishOnResume = true;
Intent i = new Intent(RateGraphActivity.this, SettingsActivity.class);
// Navigation to a sub-category doesn't seem to work yet
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
i.setAction(SettingsActivity.ACTION_PREFS_GRAPHS);
} else {
i.putExtra("settings", "graphs");
}
startActivity(i);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
finish();
}
})
.setCancelable(false);
return builder.create();
}
};
df.show(getSupportFragmentManager(), "nographs");
}
} else {
DialogFragment df = new DialogFragment() {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.graphs_not_ready)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
})
.setCancelable(false);
return builder.create();
}
};
df.show(getSupportFragmentManager(), "graphsnotready");
}
}
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,160 @@
package net.i2p.android.router.stats;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Observable;
import java.util.Observer;
import com.androidplot.Plot;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYSeries;
import com.androidplot.xy.XYStepMode;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener;
import net.i2p.android.router.util.Util;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class RateGraphFragment extends I2PFragmentBase {
// redraws a plot whenever an update is received:
private class MyPlotUpdater implements Observer {
Plot plot;
public MyPlotUpdater(Plot plot) {
this.plot = plot;
}
public void update(Observable o, Object arg) {
Util.d("Redrawing plot");
plot.redraw();
}
}
public static final String RATE_NAME = "rate_name";
public static final String RATE_PERIOD = "rate_period";
private Handler _handler;
private SetupTask _setupTask;
private SummaryListener _listener;
private XYPlot _ratePlot;
private MyPlotUpdater _plotUpdater;
public static RateGraphFragment newInstance(String name, long period) {
RateGraphFragment f = new RateGraphFragment();
Bundle args = new Bundle();
args.putString(RATE_NAME, name);
args.putLong(RATE_PERIOD, period);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_handler = new Handler();
_setupTask = new SetupTask();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_graph, container, false);
_ratePlot = (XYPlot) v.findViewById(R.id.rate_stat_plot);
return v;
}
@Override
public void onStart() {
super.onStart();
_handler.removeCallbacks(_setupTask);
_handler.postDelayed(_setupTask, 100);
}
@Override
public void onStop() {
super.onStop();
if (_listener != null && _plotUpdater != null) {
Util.d("Removing plot updater from listener");
_listener.removeObserver(_plotUpdater);
}
_handler.removeCallbacks(_setupTask);
}
private class SetupTask implements Runnable {
public void run() {
String rateName = getArguments().getString(RATE_NAME);
long period = getArguments().getLong(RATE_PERIOD);
Util.d("Setting up " + rateName + "." + period);
if (StatSummarizer.instance() == null) {
Util.d("StatSummarizer is null, delaying setup");
_handler.postDelayed(this, 1000);
return;
}
_listener = StatSummarizer.instance().getListener(rateName, period);
if (_listener == null) {
Util.d("Listener is null, delaying setup");
_handler.postDelayed(this, 1000);
return;
}
XYSeries rateSeries = _listener.getSeries();
_plotUpdater = new MyPlotUpdater(_ratePlot);
_ratePlot.addSeries(rateSeries, new LineAndPointFormatter(Color.rgb(0, 0, 0), null, Color.rgb(0, 80, 0), null));
Util.d("Adding plot updater to listener");
_listener.addObserver(_plotUpdater);
_ratePlot.setDomainStepMode(XYStepMode.SUBDIVIDE);
_ratePlot.setDomainStepValue(SummaryListener.HISTORY_SIZE);
// thin out domain/range tick labels so they dont overlap each other:
_ratePlot.setTicksPerDomainLabel(5);
_ratePlot.setTicksPerRangeLabel(3);
_ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
_ratePlot.setRangeValueFormat(new Format() {
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
double val = ((Number) obj).doubleValue();
if (val >= 10 * 1000 * 1000)
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
else if (val >= 8 * 100 * 1000)
return new DecimalFormat("0.0 M").format(val / (1000 * 1000), toAppendTo, pos);
else if (val >= 10 * 1000)
return new DecimalFormat("0 k").format(val / (1000), toAppendTo, pos);
else if (val >= 8 * 100)
return new DecimalFormat("0.0 k").format(val / (1000), toAppendTo, pos);
else
return new DecimalFormat("0").format(val, toAppendTo, pos);
}
@Override
public Object parseObject(String source, ParsePosition pos) {
return null;
}
});
Util.d("Redrawing plot");
_ratePlot.redraw();
}
}
}

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,134 @@
// License: MIT
// http://opensource.org/licenses/MIT
package net.i2p.android.router.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
/**
* Check device's network connectivity and speed.
*
* @author emil http://stackoverflow.com/users/220710/emil
* @author str4d
*/
public class Connectivity {
/**
* Get the network info.
*
* @param context the Context.
* @return the active NetworkInfo.
*/
public static NetworkInfo getNetworkInfo(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo();
}
/**
* Check if there is any connectivity at all.
*
* @param context the Context.
* @return true if we are connected to a network, false otherwise.
*/
public static boolean isConnected(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
// Works on emulator and devices.
// Note the use of isAvailable() - without this, isConnected() can
// return true when Wifi is disabled.
// http://stackoverflow.com/a/2937915
return (info != null && info.isAvailable() && info.isConnected());
}
/**
* Check if there is any connectivity to a Wifi network.
*
* @param context the Context.
* @return true if we are connected to a Wifi network, false otherwise.
*/
public static boolean isConnectedWifi(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
return (info != null && info.isAvailable() && info.isConnected() &&
info.getType() == ConnectivityManager.TYPE_WIFI);
}
/**
* Check if there is any connectivity to a mobile network.
*
* @param context the Context.
* @return true if we are connected to a mobile network, false otherwise.
*/
public static boolean isConnectedMobile(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
return (info != null && info.isAvailable() && info.isConnected() &&
info.getType() == ConnectivityManager.TYPE_MOBILE);
}
/**
* Check if there is fast connectivity.
*
* @param context the Context.
* @return true if we have "fast" connectivity, false otherwise.
*/
public static boolean isConnectedFast(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
return (info != null && info.isAvailable() && info.isConnected() &&
Connectivity.isConnectionFast(info.getType(), info.getSubtype()));
}
/**
* Check if the connection is fast.
*
* @param type the network type.
* @param subType the network subtype.
* @return true if the provided type/subtype combination is classified as fast.
*/
public static boolean isConnectionFast(int type, int subType) {
if (type == ConnectivityManager.TYPE_WIFI) {
return true;
} else if (type == ConnectivityManager.TYPE_MOBILE) {
switch (subType) {
case TelephonyManager.NETWORK_TYPE_1xRTT:
return false; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_CDMA:
return false; // ~ 14-64 kbps
case TelephonyManager.NETWORK_TYPE_EDGE:
return false; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return true; // ~ 400-1000 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return true; // ~ 600-1400 kbps
case TelephonyManager.NETWORK_TYPE_GPRS:
return false; // ~ 100 kbps
case TelephonyManager.NETWORK_TYPE_HSDPA:
return true; // ~ 2-14 Mbps
case TelephonyManager.NETWORK_TYPE_HSPA:
return true; // ~ 700-1700 kbps
case TelephonyManager.NETWORK_TYPE_HSUPA:
return true; // ~ 1-23 Mbps
case TelephonyManager.NETWORK_TYPE_UMTS:
return true; // ~ 400-7000 kbps
/*
* Above API level 7, make sure to set android:targetSdkVersion
* to appropriate level to use these
*/
case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11
return true; // ~ 1-2 Mbps
case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9
return true; // ~ 5 Mbps
case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13
return true; // ~ 10-20 Mbps
case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8
return false; // ~25 kbps
case TelephonyManager.NETWORK_TYPE_LTE: // API level 11
return true; // ~ 10+ Mbps
// Unknown
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
default:
return false;
}
} else {
return false;
}
}
}

View File

@ -0,0 +1,51 @@
package net.i2p.android.router.util;
import java.util.regex.Pattern;
import android.util.Patterns;
public class I2Patterns {
/**
* The double-parentheses are needed because the included
* pattern has an additional closing parenthesis.
*/
public static final String I2P_TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
"(?:("
+ Patterns.TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ "|i2p))";
/**
* Regular expression pattern to match most part of RFC 3987
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
* added.
* Copied from android.util.Patterns
*/
public static final Pattern I2P_WEB_URL = Pattern.compile(
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ "((?:(?:[" + Patterns.GOOD_IRI_CHAR + "][" + Patterns.GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
+ I2P_TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ "|[1-9][0-9]|[0-9])))"
+ "(?:\\:\\d{1,5})?)" // plus option port number
+ "(\\/(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ "(?:\\b|$)"); // and finally, a word boundary or end of
// input. This is to stop foo.sure from
// matching as foo.su
public static final Pattern IRC_URL = Pattern.compile(
"(irc:\\/\\/127\\.0\\.0\\.1\\:\\d{1,5})"
+ "(\\/(?:(?:[" + Patterns.GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ "(?:\\b|$)"); // and finally, a word boundary or end of
// input. This is to stop foo.sure from
// matching as foo.su
private I2Patterns() {
}
}

View File

@ -0,0 +1,33 @@
package net.i2p.android.router.util;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ToggleButton;
public class LongToggleButton extends ToggleButton {
public LongToggleButton(Context context) {
super(context);
}
public LongToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LongToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean performClick() {
/* Cancel out toggle */
toggle();
return super.performClick();
}
@Override
public boolean performLongClick() {
/* When clicked, toggle the state */
toggle();
return super.performLongClick();
}
}

View File

@ -0,0 +1,167 @@
package net.i2p.android.router.util;
import java.net.IDN;
import java.util.List;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.Page;
import net.i2p.client.naming.NamingService;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
public class NamingServiceUtil {
private static final String DEFAULT_NS = "BlockfileNamingService";
public static boolean addFromWizard(
Context ctx, NamingService ns, Bundle data, boolean replace) {
boolean success = false;
// Get the Bundle keys
Resources res = ctx.getResources();
String kHostName = res.getString(R.string.addressbook_add_wizard_k_name);
String kDest = res.getString(R.string.addressbook_add_wizard_k_destination);
String hostName = data.getBundle(kHostName).getString(Page.SIMPLE_DATA_KEY);
String host = toASCII(res, hostName); // Already validated, won't throw IAE
String displayHost = host.equals(hostName) ? hostName :
hostName + " (" + host + ')';
String destB64 = data.getBundle(kDest).getString(Page.SIMPLE_DATA_KEY);
Destination dest = new Destination();
try {
dest.fromBase64(destB64);
} catch (DataFormatException e) {} // Already validated
// Check if already in addressbook
Destination oldDest = ns.lookup(host);
if (oldDest != null) {
if (destB64.equals(oldDest.toBase64()))
Toast.makeText(ctx,
"Host name " + displayHost + " is already in address book, unchanged.",
Toast.LENGTH_LONG).show();
else if (!replace)
Toast.makeText(ctx,
"Host name " + displayHost + " is already in address book with a different Destination.",
Toast.LENGTH_LONG).show();
} else {
// Put the new host name
success = ns.put(host, dest);
if (!success)
Toast.makeText(ctx,
"Failed to add Destination " + displayHost + " to naming service " + ns.getName(),
Toast.LENGTH_LONG).show();
}
return success;
}
/** @return the NamingService for the current file name, or the root NamingService */
public static NamingService getNamingService(RouterContext ctx, String book)
{
NamingService root = ctx.namingService();
NamingService rv = searchNamingService(root, book);
return rv != null ? rv : root;
}
/** depth-first search */
private static NamingService searchNamingService(NamingService ns, String srch)
{
String name = ns.getName();
if (name.equals(srch) || basename(name).equals(srch) || name.equals(DEFAULT_NS))
return ns;
List<NamingService> list = ns.getNamingServices();
if (list != null) {
for (NamingService nss : list) {
NamingService rv = searchNamingService(nss, srch);
if (rv != null)
return rv;
}
}
return null;
}
private static String basename(String filename) {
int slash = filename.lastIndexOf('/');
if (slash >= 0)
filename = filename.substring(slash + 1);
return filename;
}
private static final char DOT = '.';
private static final char DOT2 = 0x3002;
private static final char DOT3 = 0xFF0E;
private static final char DOT4 = 0xFF61;
/**
* Ref: java.net.IDN and RFC 3940
* @param host will be converted to lower case
* @return name converted to lower case and punycoded if necessary
* @throws IAE on various errors or if IDN is needed but not available
* @since 0.8.7
*/
@SuppressLint("NewApi")
static String toASCII(Resources res, String host) throws IllegalArgumentException {
host = host.toLowerCase(Locale.US);
boolean needsIDN = false;
// Here we do easy checks and throw translated exceptions.
// We do checks on the whole host name, not on each "label", so
// we allow '.', and some untranslated errors will be thrown by IDN.toASCII()
for (int i = 0; i < host.length(); i++) {
char c = host.charAt(i);
if (c <= 0x2c ||
c == 0x2f ||
c >= 0x3a && c <= 0x40 ||
c >= 0x5b && c <= 0x60 ||
c >= 0x7b && c <= 0x7f) {
String bad = "\"" + c + "\" (0x" + Integer.toHexString(c) + ')';
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_illegal_char, host, bad));
}
if (c == DOT2)
host = host.replace(DOT2, DOT);
else if (c == DOT3)
host = host.replace(DOT3, DOT);
else if (c == DOT4)
host = host.replace(DOT4, DOT);
else if (c > 0x7f)
needsIDN = true;
}
if (host.startsWith("-"))
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_cannot_start_with, "-"));
if (host.startsWith("."))
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_cannot_start_with, "."));
if (host.endsWith("-"))
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_cannot_end_with, "-"));
if (host.endsWith("."))
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_cannot_end_with, "."));
if (needsIDN) {
if (host.startsWith("xn--"))
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_cannot_start_with, "xn--"));
if (host.contains(".xn--"))
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_cannot_contain, ".xn--"));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
return IDN.toASCII(host, IDN.ALLOW_UNASSIGNED);
else
throw new IllegalArgumentException(
res.getString(R.string.nsu_iae_requires_conversion, host));
}
return host;
}
}

View File

@ -0,0 +1,43 @@
package net.i2p.android.router.util;
import net.i2p.android.router.R;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
public class Notifications {
private final Context mCtx;
private final NotificationManager mNotificationManager;
public static final int ICON = R.drawable.ic_stat_router_active;
public Notifications(Context ctx) {
mCtx = ctx;
mNotificationManager = (NotificationManager) ctx.getSystemService(
Context.NOTIFICATION_SERVICE);
}
public void notify(String title, String text) {
notify(title, text, null);
}
public void notify(String title, String text, Class<?> c) {
NotificationCompat.Builder b =
new NotificationCompat.Builder(mCtx)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(ICON)
.setAutoCancel(true);
if (c != null) {
Intent intent = new Intent(mCtx, c);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pi = PendingIntent.getActivity(mCtx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
b.setContentIntent(pi);
}
mNotificationManager.notify(7175, b.build());
}
}

View File

@ -0,0 +1,311 @@
package net.i2p.android.router.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.preference.PreferenceManager;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.TransportManager;
import net.i2p.util.OrderedProperties;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public abstract class Util {
public static String getOurVersion(Context ctx) {
PackageManager pm = ctx.getPackageManager();
String us = ctx.getPackageName();
try {
PackageInfo pi = pm.getPackageInfo(us, 0);
//System.err.println("VersionCode" + ": " + pi.versionCode);
// http://doandroids.com/blogs/2010/6/10/android-classloader-dynamic-loading-of/
//_apkPath = pm.getApplicationInfo(us, 0).sourceDir;
//System.err.println("APK Path" + ": " + _apkPath);
if (pi.versionName != null)
return pi.versionName;
} catch (Exception e) {}
return "??";
}
/**
* Get the active RouterContext.
*
* @return the active RouterContext, or null
*/
public static RouterContext getRouterContext() {
List<RouterContext> contexts = RouterContext.listContexts();
if ( !((contexts == null) || (contexts.isEmpty())) ) {
return contexts.get(0);
}
return null;
}
private static final String ANDROID_TAG = "I2P";
public static void e(String m) {
e(m, null);
}
/**
* Log to the context logger if available (which goes to the console buffer
* and to logcat), else just to logcat.
*/
public static void e(String m, Throwable t) {
I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null)
ctx.logManager().getLog(Util.class).error(m, t);
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.ERROR)) {
if (t != null)
android.util.Log.e(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
else
android.util.Log.e(ANDROID_TAG, m);
}
}
public static void w(String m) {
w(m, null);
}
public static void w(String m, Throwable t) {
I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null)
ctx.logManager().getLog(Util.class).warn(m, t);
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.WARN)) {
if (t != null)
android.util.Log.w(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
else
android.util.Log.w(ANDROID_TAG, m);
}
}
public static void i(String m) {
i(m, null);
}
public static void i(String m, Throwable t) {
I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null)
ctx.logManager().getLog(Util.class).info(m, t);
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.INFO)) {
if (t != null)
android.util.Log.i(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
else
android.util.Log.i(ANDROID_TAG, m);
}
}
public static void d(String m) {
d(m, null);
}
public static void d(String m, Throwable t) {
I2PAppContext ctx = I2PAppContext.getCurrentContext();
if (ctx != null)
ctx.logManager().getLog(Util.class).debug(m, t);
else if (android.util.Log.isLoggable(ANDROID_TAG, android.util.Log.DEBUG)) {
if (t != null)
android.util.Log.d(ANDROID_TAG, m + ' ' + t + ' ' + android.util.Log.getStackTraceString(t));
else
android.util.Log.d(ANDROID_TAG, m);
}
}
public static List<Properties> getPropertiesFromPreferences(Context context) {
List<Properties> pList = new ArrayList<Properties>();
// Copy prefs
Properties routerProps = new OrderedProperties();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
// List to store stats for graphing
List<String> statSummaries = new ArrayList<String>();
// List to store Log settings
Properties logSettings = new OrderedProperties();
Map<String, ?> all = preferences.getAll();
Iterator<String> iterator = all.keySet().iterator();
// get values from the Map and make them strings.
// This loop avoids needing to convert each one, or even know it's type, or if it exists yet.
while (iterator.hasNext()) {
String x = iterator.next();
if ( x.startsWith("i2pandroid.")) // Skip over UI-related I2P Android settings
continue;
else if ( x.startsWith("stat.summaries.")) {
String stat = x.substring("stat.summaries.".length());
String checked = all.get(x).toString();
if (checked.equals("true")) {
statSummaries.add(stat);
}
} else if ( x.startsWith("logger.")) {
logSettings.put(x, all.get(x).toString());
} else if (
x.equals("router.hiddenMode") ||
x.equals("i2cp.disableInterface")) {
// special exception, we must invert the bool for these properties only.
String string = all.get(x).toString();
String inverted = Boolean.toString(!Boolean.parseBoolean(string));
routerProps.setProperty(x, inverted);
} else {
String string = all.get(x).toString();
routerProps.setProperty(x, string);
}
}
if (statSummaries.isEmpty()) {
routerProps.setProperty("stat.summaries", "");
} else {
Iterator<String> iter = statSummaries.iterator();
StringBuilder buf = new StringBuilder(iter.next());
while (iter.hasNext()) {
buf.append(",").append(iter.next());
}
routerProps.setProperty("stat.summaries", buf.toString());
}
pList.add(routerProps);
pList.add(logSettings);
return pList;
}
// propName -> defaultValue
private static HashMap<String, Boolean> booleanOptionsRequiringRestart = new HashMap<String, Boolean>();
private static HashMap<String, String> stringOptionsRequiringRestart = new HashMap<String, String>();
static {
HashMap<String, Boolean> boolToAdd = new HashMap<String, Boolean>();
HashMap<String, String> strToAdd = new HashMap<String, String>();
boolToAdd.put(TransportManager.PROP_ENABLE_UPNP, true);
boolToAdd.put(TransportManager.PROP_ENABLE_NTCP, true);
boolToAdd.put(TransportManager.PROP_ENABLE_UDP, true);
boolToAdd.put(Router.PROP_HIDDEN, false);
booleanOptionsRequiringRestart.putAll(boolToAdd);
stringOptionsRequiringRestart.putAll(strToAdd);
}
/**
* This function performs two tasks:
* <ul><li>
* The Properties object is modified to ensure that all options are valid
* for the current state of the Android device (e.g. what type of network
* the device is connected to).
* </li><li>
* The Properties object is checked to determine whether any options have
* changed that will require a router restart.
* </li></ul>
*
* @param props a Properties object containing the router.config
* @return true if the router needs to be restarted.
*/
public static boolean checkAndCorrectRouterConfig(Context context, Properties props) {
// Disable UPnP on mobile networks, ignoring user's configuration
// TODO disabled until changes elsewhere are finished
//if (Connectivity.isConnectedMobile(context)) {
// props.setProperty(TransportManager.PROP_ENABLE_UPNP, Boolean.toString(false));
//}
// Now check if a restart is required
boolean restartRequired = false;
RouterContext rCtx = getRouterContext();
if (rCtx != null) {
for (Map.Entry<String, Boolean> option : booleanOptionsRequiringRestart.entrySet()) {
String propName = option.getKey();
boolean defaultValue = option.getValue();
restartRequired |= (
Boolean.parseBoolean(props.getProperty(propName, Boolean.toString(defaultValue))) !=
(defaultValue ? rCtx.getBooleanPropertyDefaultTrue(propName) : rCtx.getBooleanProperty(propName))
);
}
if (!restartRequired) { // Cut out now if we already know the answer
for (Map.Entry<String, String> option : stringOptionsRequiringRestart.entrySet()) {
String propName = option.getKey();
String defaultValue = option.getValue();
restartRequired |= props.getProperty(propName, defaultValue).equals(
rCtx.getProperty(propName, defaultValue));
}
}
}
return restartRequired;
}
public static String getFileDir(Context context) {
// This needs to be changed so that we can have an alternative place
return context.getFilesDir().getAbsolutePath();
}
/**
* Write properties to a file. If the file does not exist, it is created.
* If the properties already exist in the file, they are updated.
*
* @param dir the file directory
* @param file relative to dir
* @param props properties to set
*/
public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) {
mergeResourceToFile(ctx, dir, file, 0, props);
}
/**
* Load defaults from resource, then add props from settings, and write back.
* If resID is 0, defaults are not written over the existing file content.
*
* @param dir the file directory
* @param file relative to dir
* @param resID the ID of the default resource, or 0
* @param userProps local properties or null
*/
public static void mergeResourceToFile(Context ctx, String dir, String file, int resID, Properties userProps) {
InputStream fin = null;
InputStream in = null;
try {
Properties props = new OrderedProperties();
try {
fin = new FileInputStream(new File(dir, file));
DataHelper.loadProps(props, fin);
if (resID > 0)
Util.d("Merging resource into file " + file);
else
Util.d("Merging properties into file " + file);
} catch (IOException ioe) {
if (resID > 0)
Util.d("Creating file " + file + " from resource");
else
Util.d("Creating file " + file + " from properties");
}
// write in default settings
if (resID > 0)
in = ctx.getResources().openRawResource(resID);
if (in != null)
DataHelper.loadProps(props, in);
// override with user settings
if (userProps != null)
props.putAll(userProps);
File path = new File(dir, file);
DataHelper.storeProps(props, path);
Util.d("Saved " + props.size() +" properties in " + file);
} 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) {}
}
}
}

View File

@ -1,7 +1,6 @@
package net.i2p.android.router.activity;
package net.i2p.android.router.web;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.net.Uri;
@ -21,11 +20,12 @@ import java.io.OutputStream;
import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.provider.CacheProvider;
import net.i2p.android.router.util.AppCache;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.util.EepGet;
class I2PWebViewClient extends WebViewClient {
public class I2PWebViewClient extends WebViewClient {
private BGLoad _lastTask;
/** save it here so we can dismiss it in onPageFinished() */
@ -38,10 +38,6 @@ class I2PWebViewClient extends WebViewClient {
private static final String ERROR_URL = "<p>Unable to load URL: ";
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
public I2PWebViewClient(Context ctx) {
super();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Util.d("Should override? " + url);
@ -82,7 +78,7 @@ class I2PWebViewClient extends WebViewClient {
return true;
}
if (!Util.isConnected(view.getContext())) {
if (!Connectivity.isConnected(view.getContext())) {
fail(view, "No Internet connection is available");
return true;
}
@ -187,7 +183,7 @@ class I2PWebViewClient extends WebViewClient {
******/
void cancelAll() {
public void cancelAll() {
BGLoad task = _lastTask;
if (task != null) {
Util.d("Cancelling fetches");
@ -266,14 +262,14 @@ class I2PWebViewClient extends WebViewClient {
super(view, null);
}
protected Integer doInBackground(String... urls) {
protected Integer doInBackground(final String... urls) {
publishProgress(Integer.valueOf(-1));
try {
_view.loadUrl(urls[0]);
} catch (Exception e) {
// CalledFromWrongThreadException
cancel(false);
}
_view.post(new Runnable() {
@Override
public void run() {
_view.loadUrl(urls[0]);
}
});
return Integer.valueOf(0);
}
@ -306,20 +302,20 @@ class I2PWebViewClient extends WebViewClient {
}
protected Integer doInBackground(String... urls) {
String url = urls[0];
final String url = urls[0];
Uri uri = Uri.parse(url);
File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri);
if (cacheFile.exists()) {
Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
final Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
Util.d("Loading " + url + " from resource cache " + resUri);
_view.getSettings().setLoadsImagesAutomatically(true);
_view.getSettings().setBlockNetworkLoads(false);
try {
_view.loadUrl(resUri.toString());
} catch (Exception e) {
// CalledFromWrongThreadException
cancel(false);
}
_view.post(new Runnable() {
@Override
public void run() {
_view.getSettings().setLoadsImagesAutomatically(true);
_view.getSettings().setBlockNetworkLoads(false);
_view.loadUrl(resUri.toString());
}
});
// 1 means show the cache toast message
return Integer.valueOf(1);
}
@ -341,7 +337,7 @@ class I2PWebViewClient extends WebViewClient {
if (success) {
// store in cache, get content URL, and load that way
// Set as current base
Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
final Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
if (content != null) {
Util.d("Stored cache in " + content);
} else {
@ -350,17 +346,17 @@ class I2PWebViewClient extends WebViewClient {
return Integer.valueOf(0);
}
Util.d("loading data, base URL: " + uri + " content URL: " + content);
try {
_view.loadUrl(content.toString());
} catch (Exception exc) {
// CalledFromWrongThreadException
cancel(false);
}
_view.post(new Runnable() {
@Override
public void run() {
_view.loadUrl(content.toString());
}
});
Util.d("Fetch failed for " + url);
} else {
// Load the error message in as a string, delete the file
String t = fetcher.getContentType();
String e = fetcher.getEncoding();
final String t = fetcher.getContentType();
final String e = fetcher.getEncoding();
String msg;
int statusCode = fetcher.getStatusCode();
if (statusCode < 0) {
@ -385,13 +381,14 @@ class I2PWebViewClient extends WebViewClient {
}
}
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
try {
Util.d("loading error data URL: " + url);
_view.loadDataWithBaseURL(url, msg, t, e, url);
} catch (Exception exc) {
// CalledFromWrongThreadException
cancel(false);
}
Util.d("loading error data URL: " + url);
final String finalMsg = msg;
_view.post(new Runnable() {
@Override
public void run() {
_view.loadDataWithBaseURL(url, finalMsg, t, e, url);
}
});
}
} catch (IOException ioe) {
Util.d("IOE for " + url, ioe);

View File

@ -0,0 +1,31 @@
package net.i2p.android.router.web;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import android.os.Bundle;
public class WebActivity extends I2PActivityBase {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start with the base view
if (savedInstanceState == null) {
WebFragment f = new WebFragment();
if (getIntent().getData() != null) {
Bundle b = new Bundle();
b.putString(WebFragment.HTML_URI, getIntent().getDataString());
f.setArguments(b);
} else
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}
}
@Override
public void onBackPressed() {
WebFragment f = (WebFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (!f.onBackPressed())
super.onBackPressed();
}
}

View File

@ -1,55 +1,68 @@
package net.i2p.android.router.activity;
package net.i2p.android.router.web;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
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 android.widget.TextView;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
public class WebActivity extends I2PActivityBase {
public class WebFragment extends I2PFragmentBase {
private I2PWebViewClient _wvClient;
final static String HTML_RESOURCE_ID = "html_resource_id";
public final static String HTML_URI = "html_url";
public final static String HTML_RESOURCE_ID = "html_resource_id";
private static final String WARNING = "Warning - " +
"any non-I2P links visited in this window are fetched over the regular internet and are " +
"not anonymous. I2P pages may not load images or CSS.";
@Override
public void onCreate(Bundle savedInstanceState)
{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.web);
TextView tv = (TextView) findViewById(R.id.browser_status);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.web, container, false);
TextView tv = (TextView) v.findViewById(R.id.browser_status);
tv.setText(WARNING);
WebView wv = (WebView) findViewById(R.id.browser_webview);
_wvClient = new I2PWebViewClient(this);
WebView wv = (WebView) v.findViewById(R.id.browser_webview);
_wvClient = new I2PWebViewClient();
wv.setWebViewClient(_wvClient);
wv.getSettings().setBuiltInZoomControls(true);
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
wv.getSettings().setUseWideViewPort(true);
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
String uriStr = getArguments().getString(HTML_URI);
if (uriStr != null) {
Uri uri = Uri.parse(uriStr);
//wv.getSettings().setLoadsImagesAutomatically(true);
//wv.loadUrl(uri.toString());
// go thru the client so .i2p will work too
_wvClient.shouldOverrideUrlLoading(wv, uri.toString());
} else {
wv.getSettings().setLoadsImagesAutomatically(false);
int id = intent.getIntExtra(HTML_RESOURCE_ID, 0);
int id = getArguments().getInt(HTML_RESOURCE_ID, 0);
// no default, so restart should keep previous view
if (id != 0)
loadResource(wv, id);
}
return v;
}
private void loadResource(WebView wv, int id) {
@ -76,25 +89,27 @@ public class WebActivity extends I2PActivityBase {
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
WebView wv = (WebView) findViewById(R.id.browser_webview);
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
_wvClient.cancelAll();
wv.stopLoading();
if (wv.canGoBack()) {
// TODO go into history, get url and call shouldOverrideUrlLoading()
// so we have control ??? But then back won't work right
wv.goBack();
return true;
}
public boolean onBackPressed() {
WebView wv = (WebView) getActivity().findViewById(R.id.browser_webview);
_wvClient.cancelAll();
wv.stopLoading();
if (wv.canGoBack()) {
// TODO go into history, get url and call shouldOverrideUrlLoading()
// so we have control ??? But then back won't work right
wv.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
return false;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_web_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
WebView wv = (WebView) findViewById(R.id.browser_webview);
WebView wv = (WebView) getActivity().findViewById(R.id.browser_webview);
switch (item.getItemId()) {
case R.id.menu_reload:
_wvClient.cancelAll();

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