Compare commits

...

648 Commits

Author SHA1 Message Date
idk
dec68432bc Check in translations. Fix dockerfile. Bump I2P_VERSION. Bump versionCode. 2021-11-30 19:57:51 -05:00
idk
f93fc155ef Merge branch 'Battery-Optimization-Fix' into 'master'
Battery optimization fix

See merge request i2p-hackers/i2p.android.base!2
2021-09-05 20:19:42 +00:00
e348af340a Battery optimization fix 2021-09-05 20:19:41 +00:00
idk
aa3631ed33 Merge branch 'start-on-boot-fix' into 'master'
Fixed start on boot

See merge request i2p-hackers/i2p.android.base!1
2021-09-02 16:04:07 +00:00
idk
f2088ad1e5 bump versions and tag 2021-08-30 09:58:27 -04:00
008e9b7961 Fixed start on boot 2021-08-27 04:22:06 -07:00
idk
90a46bcc3f make sure gitignore and dockerignore are checked in 2021-08-15 14:49:54 -04:00
idk
f69f748064 Checkin release 0.9.50 2021-05-19 09:32:41 -04:00
idk
640a2e1918 Add Docker release instructions, since it takes a bunch of the toolchain-management difficulty and automates it 2021-05-07 02:00:36 -04:00
idk
8aead91700 Add GPG to docker container builds 2021-05-06 17:06:16 -04:00
idk
b18a2e6241 Dockerize the build process to save time setting up releases in the future 2021-05-04 15:02:37 -04:00
idk
09c3e6e12c Remove docker instructions until I have time to write them better 2021-05-03 21:35:28 -04:00
idk
74bf0eade2 Merge branch 'master' of 127.0.0.1:i2p-hackers/i2p.android.base 2021-05-03 21:33:40 -04:00
idk
2fa6e7f3cb Enable Docker builds 2021-05-03 21:32:53 -04:00
idk
24b741be1e Enable Docker builds 2021-05-03 21:32:02 -04:00
zzz
ac8ce6f916 Update and remove old links in welcome_html 2021-05-02 11:50:14 -04:00
idk
cd7d0ad723 Android release 2021-02-28
-----BEGIN PGP SIGNATURE-----
 
 iQFMBAABCgA2FiEEcNIGBzi++AUjrK/311wDs5teFOEFAmAugowYHGhhbmtoaWxs
 MTk1ODBAZ21haWwuY29tAAoJENdcA7ObXhThwQsIAK0A284NXvJsJ7an3CCIf1Ws
 DEYmG1fibJA3BwgSYxgFUy5bgN3tEAEvwldo8PyRq6EBxe419/Ab9awoepx1wmN+
 NtfOOTuB0kHTRKUreZNuhwYUKddNrr7o10WJgHhyj6PPGIau/4XYvTVuQ70hOoI4
 Vw809u7bk5mM4cGCAnxnsgNus7khcwzbDeYZI5CbbEClk+QcKHTDkC1sMCo9vqTo
 bbS+zIoy9G+4k6LCaiC0QL6GeLcwns2vtRfDaXgImjoDGx9TL0nm/USBwsllkkmW
 +GZAvbck43yh8DixuERHN6NncuF0EI01etgvl4H8no31tQxVU+7UsZWTkz1uyKQ=
 =tJK3
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQFMBAABCgA2FiEEcNIGBzi++AUjrK/311wDs5teFOEFAmCFoG4YHGhhbmtoaWxs
 MTk1ODBAZ21haWwuY29tAAoJENdcA7ObXhThM08H/2+D0RCbP0/2QxYxFsf3NuZp
 UizMCnVqKCoomN1dChLs1kw00dJXAEAGtFZqzT8nbUHD8W98pljFc5j0qJ3BLQNU
 xrNHkE3KiwHU0ACZvrIPN9X1bj3uhNnziblPlpEBpMq/p3W8oKUWnLr9Zv8Gyetu
 Cpqjr/p4OaYpTUHLzp9NWltUpDDul0Mgy+lmhtUbYfNW4NtlVMAfOpNvBwFDcYGn
 FW4cP7t8nPzv4Cdzn5SD19KFCgGxHEFkWCF2uiJTiXOdi7LNsljKEgdMVNokO5Av
 zF97Cr+alLhg/GnxYeUB/Tgn4mTOBqzM4ir8EhIb+APWYowoWUossr68mgZ5aco=
 =BGmd
 -----END PGP SIGNATURE-----

Merge tag 'android-0.9.49'

Android release 2021-02-28
2021-04-25 13:01:31 -04:00
zzz
c5a0c3608d Drop .mtn-ignore 2021-04-25 07:11:49 -04:00
idk
3863c0e183 Increase version numbers for release 2021-02-18 10:05:20 -05:00
idk
69ea3d8bde remove trailing slash from gradle.properties 2021-02-10 17:25:32 -05:00
idk
7cafe6da48 add gitlab SSH and HTTP to i2ptunnel_config. Invalidate the view containing the I2P tunnel lists, in order to force them to redraw when a new tunnel is created. Closes #4 2021-02-04 12:41:03 -05:00
idk
9464b46ad0 Change the foreground service notification priorities from PRIORITY_MIN to PRIORITY_LOW. Don't check if we're restarting on Android versions greater than O since it uses the notificationChannel. Closes #5 2021-02-03 14:25:08 -05:00
idk
c230a5a101 Create the notification channel right before updating the statusbar notification. 2021-02-02 01:22:37 -05:00
idk
3d37f2ae07 use a toast for clipboard notification instead 2021-02-02 00:38:50 -05:00
idk
f88aafe292 fix the notification channel for the main status 2021-02-01 21:22:51 -05:00
idk
aa2dab1d3c Re-enable NTCP because it fixes the firewalled but inbound TCP not enabled issue 2021-01-29 16:45:07 -05:00
idk
8e85eaa2f0 SAM needs run() instead of start() to work correctly 2021-01-29 16:05:41 -05:00
zzz
6a1848caf6 Startup: Don't sleep in startup Jobs, requeue instead, to not clog the job queue 2021-01-27 09:09:52 -05:00
idk
aa36b4cb14 add facility to copy base32 URL of a tunnel by long-pressing the tunnel name 2021-01-18 11:58:48 -05:00
idk
78d4b12142 Remove nonsense comment at the end of the line which was covered up by the terminal 2021-01-11 10:46:15 -05:00
idk
c2f3a80dec Correctly fetch new SAM API preference when starting the SAM API job 2021-01-11 10:45:19 -05:00
idk
a679784aab Fix some build issues(Stemming from resources moving to wars) and add a SAM API option 2021-01-10 09:23:43 -05:00
idk
835667437b Fix some build issues(Stemming from resources moving to wars) and add a SAM API option 2021-01-10 09:20:45 -05:00
idk
f016edec7a add logo 2021-01-09 17:26:18 -05:00
1e3b517219 Bump target requirement to 29 for GPlay 2020-12-03 04:19:24 +00:00
f4e3b15fcf add new translation 'tk' 2020-12-02 22:51:06 +00:00
fc4154be67 Bump version for release 2020-12-02 22:48:07 +00:00
5bf0b18767 Add IceRaven configuration recommendation. 2020-11-11 05:28:17 +00:00
eb5ef3129b Add IceRaven configuration recommendation. 2020-11-11 05:26:05 +00:00
6e87d248c0 update changelog for 0.9.47-1 2020-10-30 12:39:27 +00:00
c9b0aff142 Add pushing mtn tags to the RELEASE-PROCESS.md so nobody confuses gitisms and mtnisms again 2020-10-30 02:44:26 +00:00
5acac0dbc4 increment version code 2020-10-29 19:38:28 +00:00
0b42a7ee64 add an example override.properties 2020-10-29 04:00:53 +00:00
c3a798ee3d add an example override.properties 2020-10-29 04:00:27 +00:00
e682369311 slightly better bootclasspath example in RELEASE-PROCESS.md 2020-10-28 21:40:28 +00:00
14b953f145 add classpath and Java Version requirements to RELEASE-PROCESS.md 2020-10-28 20:29:19 +00:00
036c807d6b Explicitly enable NTCP2 2020-10-28 02:00:52 +00:00
a41fca95df disable ntcp 2020-10-27 23:13:33 +00:00
26fdf40f25 merge of '5285d38e1dec9ca4af6fed9b10029de9c622a443'
and '890c3b02b48b2ff11946ccea164dad683aa2e8fd'
2020-10-27 20:29:03 +00:00
070af6529c Only ever pop the batter interstitial one time, no matter what, since we can't rely on the Android API to tell us the right thing 2020-10-27 20:28:41 +00:00
zzz
7ba0892351 Remove outproxy that's down 2020-10-26 17:30:52 +00:00
5b9cdb9f9f update release process again 2020-09-13 14:45:07 +00:00
zzz
b79d39a74a date format fixes 2020-09-03 15:07:09 +00:00
5fc5aed0c9 update the maven release instructions 2020-09-03 14:13:56 +00:00
aec25ab374 update build files for the release 2020-08-26 20:07:21 +00:00
zzz
9d5c495936 remove old subscription 2020-07-04 12:31:16 +00:00
4dc2bb6b01 check in new string for new foreground task 2020-06-29 23:28:18 +00:00
373e013911 fix foreground service notification issue on android 8.1 and greater 2020-06-29 20:12:32 +00:00
83bb7096a7 merge of '412007995e44aa866ff698a168742c4c330a62ef'
and '5bb8ab81cd3ab734f3c6b6d67d53eff28c5f5558'
2020-06-07 16:11:38 +00:00
cc1c4690a2 fix unchecked-in version code bump 2020-06-05 16:13:01 +00:00
69ad581235 update the changelog 2020-06-03 04:51:36 +00:00
4be227631d Bump version to 0.9.46 2020-06-03 00:20:01 +00:00
3f3f1f8e3d Catch the AndroidRuntimeException and warn if we can't change the power save permission 2020-06-02 22:40:58 +00:00
22290da1a4 merge of '01b01ebb27ab903f97975265856546c244185909'
and 'ac15ee2b1ce53342c8bfea5ccd3360220e615471'
2020-03-28 17:16:03 +00:00
7caf21d552 I2P Android 0.9.45 release commit 2020-03-08 04:51:08 +00:00
701860a525 Translations update in helper 2020-03-08 04:50:35 +00:00
7615aca89e Translations update 2020-03-08 04:50:07 +00:00
cae8ed2ae3 Remove arch x86 and mips due to google requirements 2020-03-08 04:49:49 +00:00
zzz
a523e1cb4a update default irc server list 2020-03-04 13:29:13 +00:00
5e048af9c1 Google said no. The 0.9.44 release would be based off this commit instead as it's has the current minimum target sdk. 2019-12-03 15:42:13 +00:00
77b6c4d30f Release commit for Android I2P 0.9.44 2019-12-03 15:33:29 +00:00
zzz
983a94e1c4 Update changelog after revert of:
Revision: 034402ca4ed4243122bd88857c7d306e295c9fbe
  Parent:   9f9a905cf75324471b3a8eb41da2feebf620207c
  Author:   meeh@mail.i2p
  Date:     10/27/2019 15:00:33
  Branch:   i2p.android.base
  Changelog:
  Merged two heads
2019-10-28 15:09:23 +00:00
zzz
1f79323d66 merge of '1d4693c3e133c94344da0418287d05613bfe34f5'
and 'cab71a0c3af65109945e3b7e4f927825e8e7deb4'
2019-10-28 15:05:50 +00:00
zzz
d86d3ad5ae disapproval of revision '034402ca4ed4243122bd88857c7d306e295c9fbe' 2019-10-28 15:05:37 +00:00
c41b064045 Android version 0.9.43 2019-10-27 15:01:28 +00:00
4c299ecda3 Merged two heads 2019-10-27 15:00:33 +00:00
761f427366 Translations update 2019-10-27 15:00:12 +00:00
f4c4bfe8be string improvements and remove redundancy in battery optimization fix 2019-10-12 17:49:06 +00:00
7f15a6f1e1 Inform the user of improved perfomance by excepting us from battery optimizations and prompt for permission to do so 2019-10-11 21:34:46 +00:00
zzz
5734760d58 Hide tunnel actions while TCG is starting 2019-10-11 17:12:33 +00:00
zzz
3244adfcd2 Rework fix for ticket #2629 to catch the null TCG sooner
javadocs
2019-10-11 16:52:43 +00:00
zzz
ce62b0fb97 Force logo to stopped if no router
Set logo for graceful shutdown
2019-10-11 16:25:43 +00:00
zzz
64673ee185 Change message for tunnel list when TCG not running yet 2019-10-11 16:20:06 +00:00
zzz
a36cabdcc8 Fix ISE in language dialog (ticket #2631) 2019-10-10 12:48:14 +00:00
1e8531c731 Fix for #2629, at least this is what we can do in the android end without adding new strings. 2019-10-09 20:19:24 +00:00
zzz
0935659d6d Save state to preferences in background thread (tickets #2595, #2632) 2019-10-08 15:45:34 +00:00
zzz
bef5f7e746 more README updates 2019-10-08 14:16:32 +00:00
zzz
d64e8359c1 Add dev build info to README 2019-10-08 14:11:20 +00:00
5fd77ea62d changelog reminder in RELEASE-PROCESS.md 2019-08-31 00:22:36 +00:00
8626ac2913 Changelog update 2019-08-31 00:21:05 +00:00
1258f18bc3 Android 0.9.42 2019-08-30 17:32:06 +00:00
8b677abd3c Hopefully a fix for #2598 2019-08-27 13:40:45 +00:00
f3d1e89002 Release commit for 0.9.41 2019-07-05 09:29:04 +00:00
zzz
f39c9a0fc4 changelog
one final
2019-06-30 16:00:48 +00:00
3186caa6bb use a timed task.get to avoid the issue in 2491 2019-06-30 15:43:35 +00:00
fc1259d8a8 fix gradle.properties 2019-06-21 14:22:01 +00:00
848d07331e fix the missing import 2019-06-21 03:28:33 +00:00
zzz
9794d26d0c Update client and helper lib min SDK to 14
Update helper support lib dependency version
2019-06-20 17:43:16 +00:00
76cd9c85ef merge of '668d7d7863ed98df6cac1ba0fe2d435570a36efb'
and 'adf749373c41853aa682a705dae3a5da0c52713b'
2019-06-20 16:30:22 +00:00
d4c7c480fb merge of '36107760d6d841ea86b860db02529fb4d089febc'
and '6f181c44ff5071894c19222047b2e90ad3ab4af5'
2019-06-20 16:24:54 +00:00
00aa80d104 fixed 2522, browsers may be installed while the application is running 2019-06-20 16:24:47 +00:00
zzz
20086685aa lint 2019-06-20 14:21:48 +00:00
zzz
86a8effd82 lint: This broadcast receiver declares an intent-filter for
a protected broadcast action string, which can only be sent by the system,
not third-party applications. However, the receiver's onReceive method
does not appear to call getAction to ensure that the received Intent's action string
matches the expected value, potentially making it possible
for another actor to send a spoofed intent with no action string
or a different action string and cause undesired behavior.
2019-06-20 14:13:10 +00:00
zzz
55d6e6d24e suppress lint 2019-06-20 13:52:37 +00:00
zzz
3b05046df3 Catch WindowManager$BadTokenException in WebViewClient (ticket #2390) 2019-06-20 13:03:37 +00:00
zzz
b8587cd0ab Don't store ContentResolver with a Context ref in AppCache static ref;
pass the Context to each call instead.
This may be the cause of some WebView leaks/crashes
2019-06-20 12:46:37 +00:00
zzz
61dd550040 lint: Remove NewsFetcher static ref;
Turn it into a ClientApp and register with the ClientAppManager
so NewsFragment can find it.
Let ClientAppManager start and stop it.
2019-06-19 15:06:38 +00:00
478ff63889 remove ! emphasis 2019-06-19 14:03:21 +00:00
e747619b85 merge of '0d35cf2746cae62a4bec33eaaf8f58302c3485aa'
and '327097d70c3dd5045a07be4e0367d91e3073a321'
2019-06-19 14:01:18 +00:00
zzz
f043bb3d72 lint: Bump espresso-core dependency from 2.2.2 to 3.0.2 2019-06-19 12:45:30 +00:00
zzz
98d8106f56 lint: Replace HashMap with SparseArray
finals
2019-06-19 12:37:01 +00:00
zzz
74eb0ce4f6 add lint notes 2019-06-18 19:37:12 +00:00
zzz
d79813d6d1 lint:
Consider using apply() instead; commit writes its data to persistent storage immediately,
whereas apply will handle it in the background

javadocs:
Unlike commit(), which writes its preferences out to persistent storage synchronously,
apply() commits its changes to the in-memory SharedPreferences immediately
but starts an asynchronous commit to disk and you won't be notified of any failures.
If another editor on this SharedPreferences does a regular commit() while a apply() is still outstanding,
the commit() will block until all async commits are completed as well as the commit itself.

As SharedPreferences instances are singletons within a process,
it's safe to replace any instance of commit() with apply() if you were already ignoring the return value.

You don't need to worry about Android component lifecycles and their interaction with apply() writing to disk.
The framework makes sure in-flight disk writes from apply() complete before switching states.
2019-06-18 19:25:44 +00:00
zzz
8f60c6ce9e lint: Specify locale in case conversion 2019-06-18 19:12:32 +00:00
zzz
7e0d017858 lint:
Must be one or more of: Service.START_FLAG_REDELIVERY, Service.START_FLAG_RETRY
2019-06-18 19:05:12 +00:00
zzz
8470435ee2 lint:
The WIFI_SERVICE must be looked up on the Application context or memory will leak on devices < Android N.
Try changing context to context.getApplicationContext()
2019-06-18 18:54:25 +00:00
5023d69222 This solves #2552 hopefully. 2019-06-18 18:09:03 +00:00
34c7464f5b fix inconsistency 2019-06-18 01:44:54 +00:00
2da6fe9c62 experimental browser documentation 2019-06-18 01:08:03 +00:00
1927c9e5a3 extend instructions for Firefox 2019-06-17 21:50:07 +00:00
1ace085d13 updating browser documentation 2019-06-17 20:09:31 +00:00
zzz
95e6c1f7a6 New 64 bit libjbigi (ticket #2503), update 32 bit jbigi
With GMP 6.1.2 and Android NDK r19c
From i2p.i2p/core/c/jbigi:
TARGET=android BITS=64 mbuild_all.sh
TARGET=android BITS=32 mbuild_all.sh
Both tested on phones.
Note lib/client/src/main/jniLibs/build.sh is broken
2019-06-05 18:37:19 +00:00
zzz
03bdd575a7 New translations: Add cs, da, el
Add missing translations to menu: ar, fi, gl, hu, zh_TW
2019-06-02 12:23:10 +00:00
zzz
98c5313d75 Add spacer between addresses in RI output 2019-06-02 11:41:45 +00:00
zzz
c047bdf085 Use new 0.9.41 TCG.getInstance() method for Android
so instance with stale context is not returned.
2019-06-02 11:10:04 +00:00
zzz
e113ef0002 build: Add streaming dependencies
(fixed streaming not found at runtime)
2019-06-01 18:40:26 +00:00
zzz
3e9b47307d i2ptunnel: Drop welterde IRC tunnel 2019-06-01 17:57:48 +00:00
zzz
2d864ad8b1 Don't start Addressbook and NewsFetcher until i2ptunnel starts
Changelog updates
2019-06-01 15:00:14 +00:00
zzz
5b7f9bd452 If router stops before ready, stop RunI2PTunnel job 2019-06-01 14:04:00 +00:00
zzz
3c89749f94 Set disableInterface property string like the others 2019-06-01 12:07:06 +00:00
zzz
edbd5fd7ea Don't zero-pad slider value 2019-06-01 12:05:45 +00:00
zzz
faf8bf74af add some padding to the tables 2019-06-01 00:25:38 +00:00
zzz
851e774e7a Update home page floating menu visibility when clicked, not just when page is loaded
Put our router info at the top of the list
Release process additions
2019-06-01 00:07:23 +00:00
zzz
dcea801116 UPnP: Add multicast permissions, use callback for MulticastLock (ticket #2499) 2019-05-31 21:51:12 +00:00
zzz
cb5235e6da add new outproxy 2019-05-31 21:26:32 +00:00
zzz
5c7eaf2484 remove BOB 2019-05-31 21:16:15 +00:00
764cfc91ec Instructions for development builds 2019-05-29 18:11:51 +00:00
177a2c6dc1 Required changes to gradle for development builds. 2019-05-29 17:37:48 +00:00
4dccd1dbae Clarify that the client library should always be released 2019-05-16 08:18:32 +00:00
50141eb24b release docs update 2019-05-13 20:03:34 +00:00
4dab632bc8 Release process docs update 2019-05-13 14:21:12 +00:00
350515041a I2P Android release commit for 0.9.40 2019-05-10 22:26:04 +00:00
5ba294c2c2 Updated the release docs with a lot more information. 2019-05-10 22:23:57 +00:00
a215363206 merge of '252f48089a9d496c609cf1b886d8d56c65836851'
and '2c8389bae9c93fb46e31edc66f1c292a95bf87eb'
2019-05-10 22:16:39 +00:00
4bd647c67c Translation updates. 2019-05-10 22:16:29 +00:00
zzz
b951892c05 Util: Check for NTCP2 in getNetStatus() 2019-05-02 14:29:59 +00:00
zzz
16e05e0dd8 Fix NPE in ViewPager (ticket #2488)
See code comments for references
2019-05-01 17:34:45 +00:00
5bdf119b81 merge of '67e6f911bbdcd72a5910ab653cf40eca13f08659'
and 'f846922ebab446b76ccee6c4d10f873af2d3d4ac'
2019-05-01 13:00:34 +00:00
b73b72c9c8 Fix for trac ticket #2485. 2019-05-01 12:59:56 +00:00
zzz
0bae211da5 Fix addressbook crash (ticket #2489) 2019-04-30 21:14:52 +00:00
zzz
709392e8b6 UI: Classify NTCP2 as NTCP
Don't crash on unknown transport configuration (ticket #2482)
2019-04-30 14:25:29 +00:00
zzz
80ed1e71da i2ptunnel: Possible NPE fix (ticket #2483) 2019-04-29 16:33:32 +00:00
6c0a60892f Fix for trac issue #2481. 2019-04-29 16:18:43 +00:00
zzz
7f13aa26fb i2ptunnel: more finals 2019-04-29 13:34:11 +00:00
zzz
7167a11844 i2ptunnel: finals 2019-04-29 13:28:18 +00:00
zzz
1f140bf95a Settings: Fix saving I2CP config (hopefully) 2019-04-29 12:47:21 +00:00
zzz
940b2b83a1 New translations: ar, gl, hu 2019-04-24 14:10:53 +00:00
zzz
bdbebe11c4 reduce min translate percentage 2019-04-24 14:07:07 +00:00
zzz
51ca137102 Update news URLs 2019-04-23 18:34:52 +00:00
f0ff4eeab7 Temporary fix for Javadoc errors 2019-04-22 07:35:37 +00:00
zzz
93d103e5ad Re-enable the advanced config for i2cp.disable,
so third-party apps can connect via the standard socket.
Domain sockets don't work on recent androids for 3rd party apps.
Bote can continue to use the domain socket if it's signed by the same key
and has the same ID.
2019-04-12 17:05:25 +00:00
b87d77d5e3 Android release commit 0.9.39
* translation updates.
* changed IPC uid to match bote.
2019-03-23 12:18:13 +00:00
ffbd8cfb76 I2P Android 0.9.38 Release commit. 2019-01-27 14:23:08 +00:00
2d1664574d "Fix" for ticket #2404 2019-01-24 18:33:26 +00:00
5d3aa1f625 Strings update 2019-01-24 18:21:20 +00:00
3fa53c7654 Expanded code to hunt NPE - ticket #2389 2019-01-24 18:21:01 +00:00
84ecf55ff8 Play store required a new android number even the first wasn't published. 2018-10-10 18:07:28 +00:00
72ad40ecfc Hotfix update cause of play console warnings.
Short: We had to upgrade to sdk 26 to ensure the app can run on all devices.
2018-10-10 17:56:38 +00:00
2f48898235 Release commit for I2P Android 0.9.37 2018-10-10 17:00:45 +00:00
39758c8cf4 Disable the SSL option for now. See trac issue #2296 2018-10-10 16:09:19 +00:00
ecc5509007 Gradle wrapper update 2018-10-10 16:07:28 +00:00
0e75b3e957 Android studio gave me some warnings against using ellipsize and maxLines together and suggested singleLine. 2018-10-10 16:06:39 +00:00
7b4c80216d Translations update. 2018-10-10 16:03:39 +00:00
70bbc18054 merge of 'bc6eaecbdac3f21b425ed335e348b1c505094d70'
and 'cd8c7bf753e03b16fd44f0516d6fd4fbd649fc5f'
2018-08-27 16:14:56 +00:00
09fcef23a4 Updates for the new i2p android 0.9.36 release. 2018-08-26 15:45:25 +00:00
zzz
333f09073a minor updates from tx 2018-07-04 13:58:17 +00:00
58cb33aa77 Updates for the new i2p android 0.9.35 release. 2018-07-03 21:42:09 +00:00
9654fa24cc Pushing 0.9.34 changes. This is the release commit. 2018-04-25 14:34:08 +00:00
7843b37a7e Release process document 2018-02-18 21:17:41 +00:00
1db9128afc 0.9.33 2018-02-17 14:57:43 +00:00
c03d3a8b92 Updated translations 2018-02-17 14:07:52 +00:00
80b7455602 Add release date to CHANGELOG 2017-11-28 10:03:51 +00:00
1fcf5aa49b 0.9.32, helper 0.9.5 2017-11-28 09:44:50 +00:00
640803418d Bump to I2P 0.9.32 2017-11-28 09:42:39 +00:00
56fa0b0302 Rename _() for translation to _t() for Java 9 compatibility 2017-11-26 13:25:58 +00:00
9c10eef0e3 Attempt to fix WindowManager$BadTokenException crash
Use the parent fragment's Context, which should be more reliable than the one
from the WebView.
2017-11-26 13:17:00 +00:00
9fd5e43115 NPE fix 2017-11-25 23:58:38 +00:00
d5bd9b8eaa Unused import 2017-11-25 23:42:01 +00:00
5b1203a1c6 IAE fix 2017-11-25 23:39:58 +00:00
fbe79eee2e Replace PNG icons with SVG
This doesn't shrink the APK size (yet), as we still generate the PNGs for older
API levels.
2017-11-25 23:31:32 +00:00
ffa21fc1e0 Drop unused icons 2017-11-25 22:39:59 +00:00
5faf1f5bb0 Add a "sync" icon to more clearly indicate tunnel "starting" status 2017-11-25 22:33:10 +00:00
e15efb6537 ActivityNotFoundException fix 2017-11-25 22:06:47 +00:00
cd1702d53c Update classname in logger.config 2017-11-25 21:06:38 +00:00
5a6ca8a0a4 Add note about possible ANR to watch for 2017-11-25 19:58:35 +00:00
82d184cf90 Fix ANR when restarting all tunnels 2017-11-25 19:43:14 +00:00
5162bb604b Delay loading of tunnels and addressbook until router is running
This should help avoid a race condition where the RouterContext is available
before the Router is fully-initialised (see ticket #2092).
2017-11-25 19:22:49 +00:00
3aff2a7a9d SecurityException fix 2017-11-25 17:01:55 +00:00
cae565761d IAE fix 2017-11-25 16:00:46 +00:00
c2b6cee9a2 ActivityNotFoundException fix 2017-11-25 15:44:47 +00:00
84e7b1f41c Update Firefox browser config instructions for Firefox Quantum 2017-11-25 15:37:48 +00:00
7ebed1e6d2 NetworkOnMainThreadException fixes 2017-11-25 14:27:10 +00:00
2e68122b8d Updated translations 2017-11-25 14:18:52 +00:00
899a3f4cfc ISE fix 2017-11-24 17:33:38 +00:00
52d49a4ab6 NPE fix 2017-11-24 17:07:49 +00:00
75f705125f ISE fix 2017-11-24 17:00:11 +00:00
722ddf8a47 NPE fix
The advanced menu options used to be in a sub-toolbar, so onPrepareOptionsMenu
was called to set their visibility. When we moved to a FAM, we didn't remove
this call, and in some newer Android versions this results in a race condition.
2017-11-24 16:21:56 +00:00
524d21631e ISE fix
Switch to using Router directly, which means we don't have a potential race
condition looking up the RouterContext.
2017-11-24 16:08:56 +00:00
86e6060217 NPE fix 2017-11-24 15:54:24 +00:00
b5c7fad876 ISE fix 2017-11-24 15:50:49 +00:00
5f3ca0fe69 NPE fix 2017-11-24 15:34:10 +00:00
ddd9bea786 Upgrade test dependencies 2017-11-24 15:13:41 +00:00
6aac99e7ea Upgrade to Gradle 4.1 and Android Gradle Tools 3.0.1
Required dropping gradle-witness because it does not support modern Gradle.
2017-11-24 14:35:07 +00:00
da763a7c81 0.9.31 2017-08-19 22:22:56 +00:00
6cb46c3168 Helper library 0.9.4 2017-08-19 22:01:34 +00:00
610e963d22 Update translations 2017-08-19 21:54:46 +00:00
96b8ed43e0 Fix NFEs 2017-08-19 00:07:22 +00:00
812c28cd33 Fix tunnel details IOOBE, expose errors to user 2017-08-18 23:20:42 +00:00
3f7312653a Update translations 2017-08-18 23:10:45 +00:00
c89d3992c7 Add Firefox Focus to unsupported browsers list 2017-08-18 23:08:48 +00:00
d9394685c9 Show tunnel status on details page, use it for transition in place of text 2017-08-18 23:04:46 +00:00
b140158b24 Fix tunnel details IAE, expose errors to user 2017-08-18 21:30:53 +00:00
4f5b0bd21a Correct Ed25519 name 2017-08-18 21:14:00 +00:00
1d17d89ccb Change default tunnel SigType to Ed25519 2017-08-18 21:13:05 +00:00
b6074da7c4 NPE fix 2017-08-18 20:41:22 +00:00
c0fdb4aff7 Validate numbers in tunnel wizard
This shouldn't be necessary, because the TextView is constrained to only accept
numbers, but someone on Google Play ran into a NumberFormatException, so...
2017-08-18 20:28:53 +00:00
e00c9cc449 Try to fix obscure NPE where none should occur 2017-08-18 19:34:55 +00:00
423ca46672 Target SDK 25
Initial testing doesn't show any permissions problems, and enabling this will
help uncover user issues.
2017-08-18 19:18:45 +00:00
66ed9d94a7 Update Firefox browser config instructions
Closes #1977.
2017-08-18 18:03:04 +00:00
a68ef9d372 Update translations 2017-08-18 16:10:26 +00:00
cb389123c5 Hide peers FAM item
From 0.9.31 the backend used to render the HTML table has been moved. We either
need to create a dedicated Android UI for this at some point, or remove it.
2017-08-18 15:39:09 +00:00
62cca0ed50 Bump I2P dependencies to 0.9.31 2017-08-18 15:36:07 +00:00
c99e3c0b41 0.9.30 2017-05-20 14:35:50 +00:00
302c51ccfa Handle IAE 2017-05-20 10:59:12 +00:00
1e34bc2159 Ensure debug build is debuggable 2017-05-20 10:39:14 +00:00
24f6f4789d Fetch WebView Context on UI thread 2017-05-20 10:31:33 +00:00
2de11a4067 Update translations 2017-05-20 09:32:23 +00:00
d83a2f9919 Updated translations 2017-05-15 12:09:43 +00:00
bf36b4c2e6 Replace anonymous DialogFragment subclasses with full subclasses
Closes #1916, #1917, #1923.
2017-05-15 12:05:10 +00:00
daa0b739a3 Use NamingService.requestUpdate()
Closes #1972.
2017-05-15 01:07:41 +00:00
5e1b0d9b50 Upgrade Android build tools, Android and I2P dependencies 2017-05-14 23:58:44 +00:00
917742847a 0.9.29 2017-03-27 03:31:16 +00:00
9460e3202f Helper library 0.9.3 2017-03-27 01:36:52 +00:00
5f388a7c6b Updated translations 2017-03-27 01:22:51 +00:00
39d5de7eb4 Upgrade I2P dependencies to 0.9.29 2017-03-27 01:20:24 +00:00
0fb1ef881c Upgrade Android dependencies 2017-03-27 00:40:05 +00:00
8230769191 Upgrade Android support libraries to 25.3.0 2017-03-26 09:22:22 +00:00
19036a71cb 0.9.28 2017-01-02 12:33:56 +00:00
40f3fbf9c5 Add -dontobfuscate to ProGuard rules 2017-01-02 12:08:30 +00:00
1127fb0195 Helper library 0.9.2 2017-01-02 12:06:26 +00:00
df81efe6bc Fix height of address book list entries 2017-01-02 10:57:40 +00:00
784ca3691b Collate TODO items 2017-01-02 10:07:26 +00:00
0fa4241ce6 New translations 2017-01-02 09:44:58 +00:00
5063d276de Updated translations 2017-01-02 09:44:39 +00:00
81d0e43f0f Upgrade Android Gradle tools to 2.2.3 2017-01-02 09:42:40 +00:00
1637a9007d Upgrade Android support libraries to 25.0.1 2017-01-02 09:41:58 +00:00
ce0f01cf46 Update I2P dependencies to 0.9.28 2017-01-02 09:35:15 +00:00
dd579d4f5b 0.9.27 2016-11-20 07:12:16 +00:00
5703d8cc6d Updated translations 2016-11-20 06:06:09 +00:00
b8768ae9fe Helper library 0.9.1 2016-11-20 05:50:53 +00:00
54dc2c88bf Make it easier to test helper library against debug I2P Android builds 2016-11-20 05:50:35 +00:00
dba01b8c18 Remove debugging lines 2016-11-20 00:18:34 +00:00
b7b3eb7019 Refactor libraries:
- Remove client library dependency on helper library
- Separate client and helper library versions             
- Bump client library version to track I2P library version
2016-11-20 00:00:03 +00:00
430e2ab826 Remove kytv's IRC server from default tunnel list 2016-11-12 19:27:39 +00:00
87383a2ec8 Upgrade Espresso to 2.2.2, update tests 2016-11-12 19:19:38 +00:00
f63bfe1dea Bump build tools to 25.0.0 2016-11-08 06:45:08 +00:00
ff2021c0aa Upgrade Android support libraries to 25.0.0 2016-11-08 06:20:05 +00:00
51f7e07080 Remove unnecessary dependency from client library 2016-11-08 06:02:07 +00:00
7797e067a5 Upgrade I2P dependencies to 0.9.27
The router JAR is now fetched instead of built locally
2016-11-08 05:52:40 +00:00
cf09a21f1e Upgrade Android Gradle plugin 2016-11-08 05:51:40 +00:00
914294927d Updated CHANGELOG 2016-06-13 13:12:50 +00:00
bd0455c413 0.9.26 2016-06-13 13:12:28 +00:00
97f3d937ee Client library 0.9 2016-06-13 12:26:32 +00:00
ff102bfe73 Update dependencies 2016-06-13 12:12:37 +00:00
e31a350398 Enable use of debug builds of I2P with release versions of helper library 2016-06-13 12:03:59 +00:00
43a8f29794 New translations 2016-06-13 11:59:59 +00:00
bbca783b20 Updated translations 2016-06-13 11:59:14 +00:00
6d4fe52f8e Fixed crash when adding tunnel to empty list 2016-06-13 09:57:58 +00:00
ecb08a54fb Upgrade Android support libraries 2016-06-13 09:56:41 +00:00
7bd4524fd8 Update README 2016-06-13 08:41:08 +00:00
40f08d56f6 Use uploaded I2P client libraries instead of locally-built 2016-06-13 08:38:43 +00:00
fe61e35146 Undo accidental checkin 2016-05-29 11:50:34 +00:00
be3f74d71f Fixed "I2CP already listening" bug 2016-05-29 05:09:51 +00:00
8dcfa816e3 Migrate to dynamically-loaded Android-specific classes
Requires i2p.i2p revision fc46f2d84625265a3899b5ad50af5e91d396ba01 or upcoming
release 0.9.26
2016-05-28 23:52:37 +00:00
ae05e22670 Fix legacy package name in helper lib 2016-05-28 23:50:46 +00:00
79a4fa0407 Upgrade Android Gradle plugin 2016-05-28 23:49:50 +00:00
bb958b969a Updated CHANGELOG 2016-04-17 14:05:34 +00:00
02030454d1 0.9.25 2016-04-17 14:00:47 +00:00
e396b0b614 Client library 0.8
i2p.i2p tag: i2p-0.9.25
2016-04-17 13:08:33 +00:00
91cac6b743 Updated CHANGELOG 2016-04-17 12:53:16 +00:00
2a6015d890 Update README 2016-04-17 12:50:47 +00:00
049b094627 Updated translations 2016-04-17 12:41:59 +00:00
077d062e19 Update ignores 2016-04-17 12:39:33 +00:00
4ad483db71 Set colour of news notification to match persistent one 2016-04-17 12:29:47 +00:00
3edb8ad0c2 Fix client lib build system 2016-04-17 12:27:52 +00:00
769f41afe6 Update Gradle wrapper to 2.12 2016-03-28 10:30:49 +00:00
5a9d943a6c Upgrade support libs in client and helper libs, partially fix ProGuard issue
A release build can now be made (ProGuard no longer complains about duplicate
JARs), but the first build attempt after a clean fails because the I2P JARs
needed by the client library aren't on the javac classpath.
2016-03-28 10:27:16 +00:00
44fb246288 Upgrade support libraries to 23.2.1 2016-03-28 03:06:46 +00:00
3cc7498e66 Upgrade Androidplot, sort remote dependencies 2016-03-28 02:33:27 +00:00
84ce883285 Missing witness hash from previous commit 2016-03-28 02:17:43 +00:00
f8dd9df285 Migrate to support library PreferenceFragmentCompat 2016-03-28 02:15:32 +00:00
860cf6a658 Update Samsung 4.2 workaround for support lib 23.1.1 2016-03-09 20:39:15 +00:00
a24a50ce44 Missing files 2016-03-05 15:34:47 +00:00
fc9187297b Update CHANGELOG 2016-03-05 00:34:00 +00:00
eb26df874d NPE fix 2016-03-05 00:33:08 +00:00
27cbb1e57b Cut off speed at KBps 2016-03-05 00:10:16 +00:00
66aa79f90f Missing from previous commit 2016-03-04 23:44:31 +00:00
8b9a70b386 Fix localisation path 2016-03-04 23:35:17 +00:00
872a2d15e2 Update translations 2016-03-04 23:27:12 +00:00
7085567a08 Try to fix ClassNotFoundException 2016-03-04 23:15:58 +00:00
19b07a8a8c Add UI for copying server tunnel B32 2016-03-04 04:49:34 +00:00
0ed78a4806 Extract UI helper and AIDL interfaces to a helper library
For apps that only want to use e.g. HTTP proxy tunnel, this will decrease the
size of their APK by over 1.3 MB.
2015-11-23 03:44:22 +00:00
ca8fb4663f Missing from previous commit 2015-11-23 03:14:33 +00:00
ec34ce481e Moved client library into subdir 2015-11-23 03:04:43 +00:00
b0c4089e26 NPE fix 2015-11-22 04:52:13 +00:00
b23148c71d Updated translations 2015-11-22 04:37:39 +00:00
c9a336a0a5 Updated dependencies 2015-11-22 04:34:43 +00:00
0744426c15 Workaround for Samsung Android 4.2 bug 2015-11-22 03:39:54 +00:00
e6f4bd5531 Updated CHANGELOG 2015-10-10 22:16:07 +00:00
ab6f4799c9 0.9.22 2015-10-10 22:10:41 +00:00
f4beecead3 Added new client library translations 2015-10-10 11:43:12 +00:00
17e6d56bb7 Add false.i2p as SSL outproxy to default httpclient tunnel 2015-10-10 11:25:16 +00:00
29881b73f9 Fixed elevation of tabs 2015-10-10 11:16:50 +00:00
8c30582b9a Updated dependencies 2015-10-10 11:07:48 +00:00
06de8abb44 Re-add fixed translations for big text notification 2015-10-10 08:43:55 +00:00
a65ee65606 Updated translations 2015-10-09 22:56:07 +00:00
a6f49168b7 Add color to notification icon 2015-10-09 11:06:40 +00:00
1d1e6121fa Show addressbook tab when selecting address 2015-10-09 02:42:49 +00:00
30b86499cd Fix tab changing on rotate 2015-10-09 01:38:10 +00:00
bb8daa81d2 Translation bugfix 2015-10-09 00:12:13 +00:00
763ce08902 Updated CHANGELOG 2015-10-08 23:43:39 +00:00
a4e1055d86 Fixed bandwidth units in notification 2015-10-08 23:43:29 +00:00
ed89afd1bd Updated translations 2015-10-08 23:38:26 +00:00
13e26b4a1c Fix Activity transitions on Lollipop 2015-10-08 23:04:26 +00:00
3be56767a1 Move Lightning to recommended, Orweb to supported 2015-10-04 05:36:45 +00:00
d184019a8e Fix CalledFromWrongThreadException 2015-06-21 00:42:58 +00:00
6df542a162 Only linkify "irc://" in first start dialog if user can open them 2015-06-20 22:36:44 +00:00
96ca1d1a37 Updated CHANGELOG 2015-06-18 05:27:54 +00:00
6b585822f1 0.9.20
i2p.i2p revision: 747e9d409223a108623b0b38d084097335d6c195
2015-06-18 05:26:44 +00:00
c81c57daa0 Updated translations 2015-06-16 11:39:36 +00:00
f28be9cb02 Updated CHANGELOG 2015-06-16 11:35:28 +00:00
6582f67ed5 Fix tunnel backup quantity fetching in advanced tunnel prefs 2015-06-16 11:35:10 +00:00
d138c482d9 Change to bar plot, update range step when plot updates 2015-06-16 09:12:28 +00:00
fc2d962cad Hide stats in final shutdown 2015-06-16 07:32:48 +00:00
c541ae0347 Remove HTML entities from clock skew error, code style 2015-06-15 11:32:19 +00:00
d6e79ed0a7 Improved graph axes 2015-06-15 11:30:54 +00:00
7ec20fe60c Updated TODO 2015-06-15 11:30:13 +00:00
f3464c5095 SU3 news 2015-06-06 10:36:12 +00:00
32512fecbc Code style 2015-06-06 07:36:16 +00:00
1d7fcd47ef Filter proxy error page resources to remove routerconsole links 2015-06-06 03:56:44 +00:00
f156c591ad Disable countries table in netDB page (because no GeoIP data) 2015-06-06 00:07:18 +00:00
fb6ca0d61e Fixed NPE (linked to previous commit) 2015-06-05 23:50:33 +00:00
a4662984a7 Added missing setContentView() 2015-06-05 23:38:49 +00:00
6cccf1fb23 Don't change lights for graceful shutdown 2015-06-05 23:13:09 +00:00
39f32acd5b Update release notes 2015-06-04 10:45:21 +00:00
3d60d10f8e Make tunnel length default 2 in tunnel prefs, to match default i2ptunnel.config 2015-06-04 10:43:23 +00:00
8cbc11dff0 Updated translations 2015-06-03 00:03:14 +00:00
858f007e21 Client library 0.7
i2p.i2p tag: i2p-0.9.20
2015-06-02 23:53:02 +00:00
02f830e472 Upgraded support libraries 2015-06-02 23:40:26 +00:00
d32ef0bb9b Unregister onLocaleChanged broadcast listener 2015-05-29 11:02:21 +00:00
6ae103373e Helper method to check if tunnels are active 2015-05-27 10:41:33 +00:00
b2a82b9809 Use state to define isStarted() 2015-05-26 03:20:40 +00:00
73b6898d76 Bugfix: hard-code IRouterState action string so it always matches the manifest 2015-05-26 03:19:11 +00:00
be9336228c New helper bind method with callback to notify caller as soon as helper is bound 2015-05-26 03:18:43 +00:00
62546a779a Updated ignores 2015-05-24 07:20:33 +00:00
b4afc3b4b2 Initial unit tests for client library 2015-05-24 07:17:53 +00:00
4415a5fc25 Updated translations 2015-05-23 22:34:41 +00:00
bddb02f6ba Handle edge cases with ViewPager disabling after shutting down router 2015-05-21 12:14:34 +00:00
f2c3f30224 Move process killing on exit into I2PActivity 2015-05-21 02:57:51 +00:00
90ff16009c Improve usage stats unit handling 2015-05-21 02:25:56 +00:00
4cd97536c5 Center text in the three console status labels 2015-05-20 12:55:30 +00:00
4f2a5fdc1f Dynamically include proxy header files
These get translated with the proxy message classes included in i2ptunnel.jar,
but the footer appended by I2PTunnelHTTPClientBase.writeErrorMessage() is not
because its strings are included in i2ptunnel.war.
2015-05-20 12:51:08 +00:00
c35d13270a Scroll padding fix 2015-05-20 12:46:43 +00:00
6c9bb31da7 Dropped unused images 2015-05-20 06:03:15 +00:00
d5f6be4428 Better side shadow for two-pane tunnel details 2015-05-20 01:56:29 +00:00
77c6e8702d Side shadow for console status 2015-05-20 01:53:29 +00:00
c4fc10f552 Bugfix 2015-05-20 01:27:46 +00:00
4b0f8eb571 Performance tweak 2015-05-20 00:20:21 +00:00
f94d2c57db Move findViewById() calls into onCreateView() for efficiency 2015-05-20 00:18:20 +00:00
fc2d6f5f09 Fix landscape view when router not running 2015-05-19 23:59:37 +00:00
c6ab6f64e9 Reorganized console status view to only show net status when no internet 2015-05-19 23:54:07 +00:00
5315771118 Updated translations 2015-05-19 23:30:22 +00:00
39b8bff6a2 Updated translations 2015-05-19 11:42:46 +00:00
c2a7c8ebde String tweaks 2015-05-19 11:38:54 +00:00
32a8c71bf1 Show net status level 2015-05-19 11:32:30 +00:00
f35257fc65 Handle all net statuses, extract console strings 2015-05-19 11:22:07 +00:00
3ab1fa7c97 Extracted more strings for translation 2015-05-18 23:13:36 +00:00
551910abce Updated translations 2015-05-18 02:13:36 +00:00
170f8afafd Console layout fixes 2015-05-17 12:31:08 +00:00
20c08b1929 New console status layout 2015-05-17 12:22:21 +00:00
e943c31ef9 Improve layout performance 2015-05-17 06:44:25 +00:00
601b979d4e Re-increase routerlogo size, center portrait, fix view bounds issue 2015-05-17 05:15:19 +00:00
973808b392 Reduced size of routerlogo, moved usage stats to bottom toolbar 2015-05-17 05:01:02 +00:00
25811b742f Do full i2p.i2p rebuild after clean 2015-05-15 09:29:16 +00:00
2d57d7cad7 Updated translations 2015-05-15 08:13:03 +00:00
687a62656b Extracted remaining hard-coded strings that won't be changed 2015-05-15 08:05:53 +00:00
5a7c33d9b9 Updated translations 2015-05-15 05:44:31 +00:00
e66c98fbfe Language name tweak 2015-05-14 06:31:56 +00:00
2897bfcb77 Updated translations after string push 2015-05-13 11:34:18 +00:00
b8aca7badc Updated translations 2015-05-13 11:14:39 +00:00
edb2506083 Renamed addressbook page headers 2015-05-13 11:05:06 +00:00
eceb7f14a4 Change "No client tunnels..." to "No tunnels..." 2015-05-13 10:47:35 +00:00
b165f41266 Separate "graphs not ready" dialog, make dialogs non-cancelable 2015-05-13 10:45:01 +00:00
9dd15e550d Added icons to main settings categories 2015-05-12 10:41:53 +00:00
e94984901d Moved graphs from advanced to general 2015-05-12 10:17:06 +00:00
55d682fdfc Updated translations 2015-05-12 05:27:46 +00:00
c231a9f851 Fixed libjbigi building for x86 and mips 2015-05-11 12:13:12 +00:00
bbde471e99 Updated target SDK and build tools to API 22 2015-05-11 11:28:08 +00:00
51c5409f12 Updated Gradle plugin version 2015-05-11 11:24:10 +00:00
1ff96d5530 Updated translations after removing strings 2015-05-11 10:13:53 +00:00
5e099d0e3b Removed some old strings 2015-05-11 10:09:59 +00:00
c27e8ff513 Updated translations 2015-05-11 10:04:34 +00:00
632ed5b4b3 Extract more hard-coded strings 2015-05-11 09:44:35 +00:00
407b5c1441 Moved start/stop/restart all tunnels to TunnelsContainer 2015-05-10 13:41:14 +00:00
fd2050bc1f Updated translations 2015-05-09 13:35:59 +00:00
c615497a9e Translate news strings 2015-05-09 13:30:40 +00:00
e48b2f1dff Translate "shared clients" properly in-house 2015-05-09 13:21:31 +00:00
44cd0a6d55 Update router.config with language 2015-05-09 11:06:57 +00:00
196b6ffbbc Fixed plurals to match Transifex language rules 2015-05-08 13:17:30 +00:00
6cf01e3db0 Updated translations 2015-05-08 13:01:33 +00:00
204f2d8adc Lint 2015-05-08 13:01:14 +00:00
93853bd6b6 Lint: Unnecessary (un)boxing 2015-05-08 11:23:13 +00:00
4095b48a82 Lint 2015-05-08 11:20:49 +00:00
3ab1a68b94 Collated constants 2015-05-08 11:03:22 +00:00
7675d78d0d Converted hard-coded log strings to translatable strings 2015-05-08 10:59:19 +00:00
1b2fa9bda6 Rate graph styling 2015-05-08 10:36:01 +00:00
8819bbfa44 Removed redundant text 2015-05-08 08:58:46 +00:00
2e6ff0ac07 Fix default graphs 2015-05-08 08:18:22 +00:00
8d9a532424 Fixed console FAM visibility 2015-05-08 04:15:50 +00:00
6f5e3e2386 Replaced hard-coded activity titles with translatable strings 2015-05-08 04:07:30 +00:00
fb63f1eee7 Material design for TextResourceDialog 2015-05-07 02:08:20 +00:00
8c65812fa0 Add Ok button to FirstStartDialog 2015-05-07 01:37:55 +00:00
adfc6415b3 Display up button on Licenses 2015-05-07 01:37:39 +00:00
bb2a1c7c62 Prevent race condition if language choice restarts Activity 2015-05-06 22:15:38 +00:00
3d63269286 Choose language on first start 2015-05-06 13:04:53 +00:00
6dde8d2a88 Added Ok button to About dialog 2015-05-06 11:45:31 +00:00
d0c6bcff7d Tunnel interfaces for bidir and Streamr server tunnels 2015-05-06 06:50:26 +00:00
b938bc9698 NPE fix 2015-05-06 02:34:35 +00:00
2cb877d61b List available interfaces in tunnel preferences 2015-05-06 02:34:06 +00:00
77368e370b Updated translations 2015-05-06 02:04:50 +00:00
2e6589ea74 Status bar fixes 2015-05-06 01:56:28 +00:00
90367b0f9c Mark notification statuses for translation 2015-05-06 01:46:58 +00:00
97a350c482 Fixed deprecation 2015-05-06 01:26:53 +00:00
57d7708ae6 Enable language to be changed from default 2015-05-05 04:17:14 +00:00
913d39a9a2 Updated language code rewrites
See https://developer.android.com/reference/java/util/Locale.html for details
2015-05-05 03:15:50 +00:00
fa83742386 New client translations for ca 2015-05-05 03:14:42 +00:00
94539b8ebb Updated translations 2015-05-05 03:14:16 +00:00
bccbe30074 AppCompat v22.1.*: more deprecations 2015-05-02 11:54:21 +00:00
4e954eef50 AppCompat v22.1.*: Material design dialogs 2015-05-02 11:27:39 +00:00
6eeafb64f5 AppCompat v22.1.*: deprecations 2015-05-02 11:14:33 +00:00
d7184e9c99 Add up nav to one-pane tunnel details 2015-05-02 11:09:04 +00:00
be8016c02c Display selected tunnel setting in ListPreference summary 2015-05-02 04:59:32 +00:00
11d4e05518 Fix tunnel preference ordering 2015-05-02 00:02:19 +00:00
fdccadda04 Prevent PERSISTENT_KEY and NEW_KEYS from being set simultaneously 2015-05-01 23:42:46 +00:00
ccb1c73cf3 Lint 2015-05-01 12:29:39 +00:00
f3168f0dc2 Updated CHANGELOG 2015-05-01 12:08:40 +00:00
b4210cfb33 propagate from branch 'i2p.android.base.fragments' (head d590551a58afc8cebc67722afc73494e4f8836c0)
to branch 'i2p.android.base' (head e20ea0056b24eb0050ae3be6d84aac6e5157503d)
2015-05-01 10:25:16 +00:00
6e0a66292d Lint 2015-05-01 09:57:13 +00:00
71856397de Lint 2015-05-01 09:01:09 +00:00
568cdeca36 Lint: Rename values-id to values-in
See java.util.Locale for details
2015-05-01 08:58:15 +00:00
4e64fb3095 Updated translations 2015-05-01 08:48:16 +00:00
f33f26987b Fix adding entries to private addressbook 2015-05-01 08:39:13 +00:00
99fa7fbec6 Fix loading B64 from file 2015-05-01 08:37:35 +00:00
315931a032 Move from Maven Central to JCenter, fix up some dependencies
- JCenter is now the Android Studio default, it imports from MC
- Updated to materialish-progress 1.5
- Fixed viewpagerindicator hash (author accidentally overwrote it on MC)
2015-04-29 12:52:31 +00:00
8c63fb0ce2 Updated Android plugin
The IDE class path issue is fixed with Android Studio 1.2
2015-04-29 12:16:41 +00:00
31b7e18aad Updated Android support libraries
According to https://code.google.com/p/android/issues/detail?id=160591#c16 the
button theme bug is fixed. I still see it with API 10 (but fixed in 19+) in the
Android Studio renderer, but apparently it does work on Gingerbread devices.
2015-04-29 12:14:42 +00:00
b51ca8b27c Fix child fragments not getting an updated two-pane status on screen rotate 2015-04-28 12:51:22 +00:00
13af3d7869 Fix default router lights in landscape 2015-04-28 12:50:32 +00:00
c91de6f7ab Don't allow users to stop the router until it has finished starting 2015-04-25 05:35:08 +00:00
d961b5f8b1 Added scrolling to tunnel details 2015-04-21 13:05:07 +00:00
7d8141f62b Downgrade Android Support libraries to 21.0.3
Waiting on this issue to be resolved:
https://code.google.com/p/android/issues/detail?id=160591
2015-04-21 12:16:38 +00:00
6ef5d793f4 Save and restore console light status 2015-04-21 07:49:24 +00:00
c5c97366b8 Create the WizardModel before super.onCreate() to prevent NPEs in Fragments 2015-04-21 05:15:40 +00:00
124692db8e Reverse the Lollipop transition when finishing TunnelDetailActivity 2015-04-21 03:58:16 +00:00
72461cb678 Lollipop shared-element transition when opening TunnelDetailActivity 2015-04-21 03:52:29 +00:00
6f84f3ce06 Enable devices on API 21+ to use nice transitions 2015-04-21 03:08:05 +00:00
cbe21d94c9 Fix reloading of toolbar to update start/stop buttons in TDF 2015-04-21 02:47:19 +00:00
9c8c462089 Fixed up navigation issues from EditTunnelActivity and SettingsActivity 2015-04-20 22:56:09 +00:00
cc7c67c494 Updated TODO 2015-04-20 12:45:59 +00:00
0e14ed20e6 Split tunnel preferences into two Fragments
This prevents the advanced PreferenceScreen from going fullscreen.
2015-04-20 12:44:07 +00:00
d620535246 Fix ISE if screen rotating while router state changes 2015-04-20 12:40:19 +00:00
350d8a1363 Handle ActionBar up nav properly in SettingsActivity 2015-04-20 05:19:17 +00:00
23e80ec72b Split preferences into individual support PreferenceFragments 2015-04-20 04:11:05 +00:00
fd052f1b38 "Fix" race between RouterContext initialization and first console status update 2015-04-19 12:17:06 +00:00
865f5d271a Updated translations 2015-04-19 11:39:33 +00:00
d1293f5949 Mark property strings as non-translatable 2015-04-19 11:36:49 +00:00
2a354288bf Updated TODO 2015-04-19 11:26:14 +00:00
f20e82a25e Scroll entire console fragment in portrait (so stats are more visible on phones) 2015-04-19 11:24:23 +00:00
4a7899ce59 Update menus, FAMs etc. on router state change 2015-04-19 07:12:04 +00:00
2adc368307 Clear console status info as soon as the router has stopped 2015-04-19 07:02:12 +00:00
2bb57d2c4d Divider and margins for addressbook two-pane view 2015-04-19 05:15:07 +00:00
e649ac448b Added link icon to tunnel details page 2015-04-19 05:02:23 +00:00
337c93bb6c Updated TODO 2015-04-19 04:47:25 +00:00
f5624cf259 Differentiate between router shutting down and not running when tabs disabled 2015-04-19 04:46:28 +00:00
6afd9b950d Remove padding from top of addressbook list (headers do the job) 2015-04-19 04:45:56 +00:00
94c46d4a43 Divider for tunnel details, fixed strings 2015-04-19 04:33:05 +00:00
dcf072905c Move tunnel open link to details page 2015-04-19 04:19:52 +00:00
c67aeddb3b Material design: master-detail tunnel view
Detail shadowing method credit:
http://sapandiwakar.in/adding-shadows-to-views-in-android-using-9-patch-image/
2015-04-19 03:52:51 +00:00
f125079aa5 Added elevation to main toolbar
Pre-21 shadow uses code from the I/O 2014 source:
https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/DrawShadowFrameLayout.java
2015-04-19 03:49:41 +00:00
0ddd9e5f4c Material design tablet margins 2015-04-19 02:22:39 +00:00
0074cf5a19 Use separate toolbar for tunnel details menu 2015-04-19 02:22:11 +00:00
19082db9a6 Start unifying list styles, first pass at improving tunnel details 2015-04-19 01:06:59 +00:00
109aeb7796 Disable tunnels and addressbook when router not running 2015-04-18 12:38:38 +00:00
3e06a11017 Updated TODO 2015-04-18 12:37:57 +00:00
dafd438982 Common style for pager indicators 2015-04-18 03:57:37 +00:00
063fd63fe2 Updated comment to match 2015-04-18 03:18:25 +00:00
5e27a93df9 Only show tunnels in two-pane for w720dp 2015-04-18 03:14:59 +00:00
c428195127 Attempted fix of IllegalStateException 2015-04-18 00:55:21 +00:00
1fba23a144 Use correct parent for TunnelDetailListener 2015-04-18 00:41:48 +00:00
3cc1e2b4bb Hide unusable console options when router is not running 2015-04-18 00:39:28 +00:00
77b05fa6e0 Open correct help page for correct tab 2015-04-18 00:32:55 +00:00
3553065ce2 Don't show start/stop/restart until TCG has started 2015-04-18 00:28:35 +00:00
167c06225f Added missing FAB to two-pane tunnels container 2015-04-18 00:10:00 +00:00
3420c04735 Improved title indicator for container ViewPagers 2015-04-17 23:42:50 +00:00
973282ab59 Updated TODO 2015-04-17 22:22:50 +00:00
7c04a96639 Re-enable tunnel editing 2015-04-17 22:22:36 +00:00
b2db1f4a6c Start TCG in LoadClientsJob 2015-04-17 11:24:08 +00:00
e3e0960ed8 Test settings navigation (to help pick up bugs like the IntListPreference one) 2015-04-16 11:24:08 +00:00
78ad153c47 Fixed integration tests for swapped tabs 2015-04-16 11:11:58 +00:00
fa6ba6bdce Updated CHANGELOG 2015-04-16 00:52:06 +00:00
8f302e6eeb 0.9.19.1
i2p.i2p tag: 0.9.19
2015-04-16 00:51:40 +00:00
93d2677b34 Fix for when an IntListPreference was previously stored in a ListPreference 2015-04-16 00:51:15 +00:00
1d02535158 StackOverflowException fix 2015-04-15 23:30:29 +00:00
955e40b0fc Updated client library dependencies 2015-04-13 12:06:01 +00:00
b4367843cd Gradle build script lint 2015-04-13 12:05:23 +00:00
76fd69544d Updated library versions 2015-04-13 12:03:18 +00:00
99b582a005 propagate from branch 'i2p.android.base' (head d8a24ace7d2d7616fcc2b12ac9a663f4cdbb7be8)
to branch 'i2p.android.base.fragments' (head 376be71890e893e97d2b8fed60d2a567c235ba99)
2015-04-13 08:29:00 +00:00
8e0e5ed5c4 Updated CHANGELOG 2015-04-13 02:21:38 +00:00
234bc6e5a0 0.9.19
i2p.i2p tag: i2p-0.9.19
2015-04-13 02:20:51 +00:00
b0131843ae Handle saved strings from older version 2015-04-13 01:52:24 +00:00
c32bda66b5 Client library 0.9.19
i2p.i2p tag: i2p-0.9.19
2015-04-13 01:18:21 +00:00
6d726df1dc Updated translations 2015-04-13 00:01:20 +00:00
5b5a99f512 Removed duplicate info from tunnel details page 2015-04-10 10:32:19 +00:00
a11dd1e4e6 Rebuilt libjbigi.so for armeabi with new build.sh, new build for armeabi-v7a
Both tested on my local device, router starts and builds tunnels.

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

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
app/pkg-temp
app/build
app/pkg-mavencentral

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
etc/docker.signing.properties
signing.properties

View File

@ -1,51 +0,0 @@
# Just to try and prevent some noob disasters.
# Use mtn add --no-respect-ignore foo.jar to ignore this ignore list
_jsp\.java$
\.bz2$
\.class$
\.diff$
\.exe$
\.fba$
\.gz$
\.jar$
\.out$
\.patch$
\.sig$
\.sud$
\.su2$
\.tar$
\.war$
\.zip$
^\.
^build/
^pkg-temp/
~$
/build/
/classes/
# Android-specific ignores
^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,18 +1,18 @@
[main]
host = https://www.transifex.com
lang_map = pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, zh_CN: zh
lang_map = he: iw, id: in, pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, yi: ji, zh_CN: zh, zh_TW: zh-rTW
[I2P.android]
file_filter = app/src/main/res/values-<lang>/strings.xml
minimum_perc = 22
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
[I2P.android_lib_helper]
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
minimum_perc = 50
source_file = lib/helper/src/main/res/values/strings.xml
source_lang = en
type = ANDROID
minimum_perc = 50

161
CHANGELOG
View File

@ -1,11 +1,168 @@
0.9.18
0.9.50 2021-05-18
* This release updates the underlying I2P libraries to I2P version 0.9.50
0.9.48 2020-12-02
* This release updates the underlying I2P libraries to I2P version 0.9.48
* Updates to browser configuration documentation
0.9.47-1 2020-10-29
* This release fixes a number of bugs arising from a misconfigured bootclasspath
* Fix a battery-management issue arising on some phones
0.9.47 / 2020-08-26
* Notification bug-fixes on platforms >8.0
0.9.46 / 2020-06-03
* catch ActivityNotFound exception in MainActivity
0.9.45 / 2020-03-07
* No significant changes
0.9.44 / 2019-12-03
* Updated translations
* Bumped target sdk version to 28, enforced by google
0.9.43-1 / 2019-10-28
* Fix crash at startup in TCG
0.9.43 / 2019-10-27
* Save state in background thread (tickets #2595, #2632)
* Fix ISE in language dialog (ticket #2631)
* Fix NPE in create tunnel (ticket #2629)
* Update logo after router killed in background
* Fix message in tunnels tabs when tunnels not up yet
* Hide tunnel actions while TCG is starting
* Add battery permissions dialog (ticket #2607)
0.9.42 / 2019-08-28
* Possible fix for tunnel edit dialog crash (ticket #2598)
0.9.41 / 2019-07-03
* New 64 bit libjbigi (ticket #2503)
* Update 32 bit jbigi to GMP 6.1.2
* Fix for client tunnels not starting after reseed
* UPnP Fixes (ticket #2499)
* WebView crash fixes (ticket #2390)
* i2ptunnel crash fix (ticket #2552)
* i2ptunnel ANR fix (ticket #2491)
* Browser help pages improvements (tickets #2521, 2523)
* Code cleanups and fixes
* Update visibility of floating menu items
* Put our router info at top of list
* Table layout cleanups
* Fixes for threads remaining after router stop
* Remove BOB
* Remove welterde IRC tunnel
* Various lint fixes
* New translations: Add cs, da, el
* Add missing translations to menu: ar, fi, gl, hu, zh_TW
0.9.40 / 2019-05-10
* Open local I2CP socket for 3rd party apps
* Fix News URLs
* Numerous bug fixes (see trac)
0.9.39 / 2019-03-23
* Set App ID
0.9.38 / 2019-01-27
0.9.37 / 2018-10-10
0.9.36 / 2018-08-27
0.9.35 / 2018-07-03
0.9.34 / 2018-04-25
0.9.33 / 2018-02-18
* Translation updates
0.9.32 / 2017-11-28
* Fixed "Application Not Responding" error when restarting all tunnels
* Fixed crashes when:
* opening the console menu
* starting the router
* starting the router for the first time
* viewing tunnels
* viewing the addressbook
* opening the addressbook menu
* configuring addressbook subscriptions
* using a configuration wizard
* loading a B64 Destination from file
* viewing tunnel settings
* saving tunnel settings
* installing a tunnel's recommended app without a market app
* installing a browser without a market app
* rotating the screen while using the built-in browser
* Added a "sync" icon to more clearly indicate tunnel "starting" status
* Updated Firefox browser config instructions for Firefox Quantum
* Translation updates
0.9.31 / 2017-08-19
* Fixed various crashes in the Tunnels UI
* Updated Firefox browser config instructions
* Minor bug fixes
* Dependency and translation updates
0.9.30 / 2017-05-20
* Fixed crashes when creating or deleting tunnels, or adding names to the
private addressbook
* Minor bug fixes
* Dependency and translation updates
0.9.29 / 2017-03-27
* Dependency and translation updates
0.9.28 / 2017-01-02
* Bug fixes and translation updates
0.9.27 / 2016-11-20 / 64ff68efe98c345acb6ba1d0432fa49d1d650358
* Removed kytv's IRC server from default tunnel list
* Translation updates
0.9.26 / 2016-06-13 / b003272c8b504bb0d904edca2e95359a57c9a52c
* Fixed "I2CP already listening" bug
* Fixed crash when adding tunnel to empty list
* Translation updates
0.9.25 / 2016-04-17 / 46d45a878a2b73394b26ca27dbe6c696dedcf1c3
* Fixed a bug on Samsung Android 4.2 devices
* Dependency improvements
* Translation updates
0.9.22 / 2015-10-10 / 0f73ef90b81e2cf3d55f0ea2b0a16e1f10da40ad
* Updated browser config guide
* Bug fixes and translation updates
0.9.20 / 2015-06-18 / 5fdaabeb5fa955caac90f1390adbdeaeae42fdf1
* Simplified the main interface
* Language can be configured
* Tunnels can now be edited
* Material design improvements
* Better support for tablets
* Improved graph rendering
* Bug fixes and translation updates
0.9.19.1 / 2015-04-15 / ed86e7e85161dbe3f15932fd4d195c551f8e2c71
* Fixed crash when opening advanced settings
0.9.19 / 2015-04-13 / 3cfb748946a5876dc06d5f81d811b142a88846f7
* Made internal state handling more stable
* Added graceful shutdown support
* Updated libjbigi to use GMP 6.0.0
* New libjbigi binary for armeabi-v7a to speed up newer devices
* Improved logging
* Bug fixes and translation updates
0.9.18 / 2015-03-04 / c2f4831a1617f4ce716a08640446fdd992c751ff
* I2P can start automatically when phone boots (configure in Setting)
* Updated browser configuration guides for Orfox and Firefox
* Tunnels for postman's mail server added to defaults (for new installs)
* Settings options for configuring UDP and TCP ports
* Bug fixes and translation updates
0.9.17.1 /2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
0.9.17.1 / 2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
* Fixed crashes in addressbook and netDb status page
* Fixed crash when opening an IRC client tunnel
* Updated translations

11
DEVELOPMENT-README.md Normal file
View File

@ -0,0 +1,11 @@
# Development Readme
## How to build development builds of the router core for android?
Check the RELEASE-PROCESS.md file for general information about how to build and to bump the version.
In your i2p.i2p codebase checkout, execute `./installer/resources/maven-dev-release.sh` with your build number as first argument.
The script locates itself and uses the same codebase as it's in, to produce the maven builds which will be locally installed.
Next, add the build number to the gradle.properties and build the android build as usual.

91
DOCKER.md Normal file
View File

@ -0,0 +1,91 @@
Docker Build Instructions
=========================
It is possible to build a container with a pre-installed environment for
correctly compiling an I2P for Android development build. Unlike the i2p.i2p
container, zero attempt has been made to optimize the size of the container,
as it contains a copy of the latest Android SDK, toolchains, and Android NDK,
which it must download. To save time, this is cached locally. It is likely to
take up to 30 GB of disk space to compile in this way, however, it is very easy
and convenient compared to the steps in RELEASE-PROCESS.md and may make
building Android reproducibly easier in the future.
Container dependencies
----------------------
- `menny/android_ndk` (third-party image) (reviewed by idk) (depends on menny/android_sdk
- `menny/android_sdk` (third-party image) (reviewed by idk) (depends on ubuntu/18.04)
- `ubuntu/18.04` (official docker container) (base container)
Build the container locally
---------------------------
Run:
docker build -t i2p.android.base .
To build the container. It will have a lot to download the first time, so it may take
a while to complete.
Run an Android build in the container
-------------------------------------
Copy the `etc/docker.signing.example.proprties` file to `etc/docker.signing.proprties`,
edit it to match your key information and rebuild the container.
Run:
docker run -it \
-u $(id -u):$(id -g) \
--name i2p.android.base \
-v $HOME/.gnupg/:/.gnupg/:ro \
-v $HOME/.i2p-plugin-keys/:/.i2p-plugin-keys/:ro \
-v /run/user/$(id -u)/:/run/user/$(id -u)/:ro \
i2p.android.base
To get the build artifacts for uploading to Maven out of the container, use:
docker cp i2p.android.base:/opt/workspace/i2p.i2p/pkg-mavencentral app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-i2p.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-mstreaming.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-router.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-servlet-i2p.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-streaming.jar app/pkg-mavencentral
To get the Android build artifacts out of the container, use:
docker cp i2p.android.base:/opt/workspace/i2p.android.base/app/build/ app/build
And your android applications will appear in the `app/build` directory, in the same
place where non-container builds would go.
If you encounter a permissions error when rebuilding, delete the `app/build`,
`app/pkg-mavencentral` and `app/pkg-temp` path.
rm -rf app/pkg-temp app/build app/pkg-mavencentral
Copypasta
---------
Once you have set up builds for the first time, from then on you can update the container and
build a fresh set of Maven jars and a new I2P for Android app by copy-pasting the following
commands:
``` sh
rm -rf app/pkg-temp app/build app/pkg-mavencentral
docker build -t i2p.android.base .
docker run -it \
-u $(id -u):$(id -g) \
--name i2p.android.base \
-v $HOME/.gnupg/:/.gnupg/:ro \
-v $HOME/.i2p-plugin-keys/:/.i2p-plugin-keys/:ro \
-v /run/user/$(id -u)/:/run/user/$(id -u)/:ro \
i2p.android.base
docker cp i2p.android.base:/opt/workspace/i2p.i2p/pkg-mavencentral app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-i2p.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-mstreaming.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-router.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-servlet-i2p.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-streaming.jar app/pkg-mavencentral
docker cp i2p.android.base:/opt/workspace/i2p.android.base/app/build/ app/build
```

39
Dockerfile Normal file
View File

@ -0,0 +1,39 @@
FROM menny/android_ndk
ENV VERSION=0.9.50
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
RUN echo 'deb http://deb.i2p2.de/ sid main' >> /etc/apt/sources.list
RUN echo 'deb-src http://deb.i2p2.de/ sid main' >> /etc/apt/sources.list
RUN echo 'deb http://archive.ubuntu.com/ubuntu trusty universe' >> /etc/apt/sources.list
RUN wget -O /etc/apt/trusted.gpg.d/i2p-debian-repo.key.asc https://geti2p.net/_static/i2p-debian-repo.key.asc
COPY etc/debian-jessie-repo.key.asc /etc/apt/trusted.gpg.d
RUN mkdir -p /opt/packages && wget -O /opt/packages/openjdk-7-jre-headless.deb http://security.debian.org/debian-security/pool/updates/main/o/openjdk-7/openjdk-7-jre-headless_7u261-2.6.22-1~deb8u1_amd64.deb
RUN apt-get update
RUN apt-get build-dep -y i2p i2p-router
RUN apt-get install -y ant openjdk-8* libxml2-utils junit4 libhamcrest-java libmockito-java libmaven-ant-tasks-java dpkg-sig maven
RUN cd /opt/packages && dpkg-sig -l openjdk-7-jre-headless.deb && dpkg -x openjdk-7-jre-headless.deb /opt/packages/openjdk-7-jre
RUN git clone https://github.com/i2p/i2p.i2p --depth=1 -b i2p-$VERSION /opt/workspace/i2p.i2p
RUN update-java-alternatives --jre-headless --set java-1.8.0-openjdk-amd64
RUN update-java-alternatives --set java-1.8.0-openjdk-amd64
RUN update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac
RUN update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
RUN rm /opt/java/openjdk/ -rfv
COPY . /opt/workspace/i2p.android.base
COPY etc/docker.local.ndk.properties /opt/workspace/i2p.android.base/client/local.properties
COPY etc/docker.local.router.properties /opt/workspace/i2p.android.base/routerjars/local.properties
COPY etc/docker.local.sdk.properties /opt/workspace/i2p.android.base/local.properties
COPY etc/docker.override.properties /opt/workspace/i2p.android.base/override.properties
COPY etc/docker.override.properties /opt/workspace/i2p.i2p/override.properties
COPY etc/docker.signing.properties /opt/workspace/i2p.android.base/signing.properties
WORKDIR /opt/workspace/i2p.android.base
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chown -R 1000:1000
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chmod -Rc o+rw
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chmod -c 0755
RUN find /opt/workspace -type d -print0 | xargs -0 chown -R 1000:1000
RUN find /opt/workspace -type d -print0 | xargs -0 chmod -Rc o+rw
RUN find /opt/workspace -type d -print0 | xargs -0 chmod -c 0755
CMD cd /opt/workspace/i2p.i2p && \
ant -k mavenCentral; \
cp -v *.jar pkg-mavencentral/; \
cd /opt/workspace/i2p.android.base && \
./gradlew --continue dependencies || true ; \
./gradlew --continue assembleRelease; tail -f README.md

View File

@ -7,10 +7,10 @@
- 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 SDK for API 28 or higher
- Android Build Tools 28.0.0 or higher
- Android Support Repository
- Gradle 2.1
- Gradle 2.2.1
### Gradle
@ -48,6 +48,12 @@ systemProp.socksProxyPort=9150
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
3. Create a `local.properties` file in `i2p.android.base/lib/client` containing:
```
ndk.dir=/path/to/ndk
```
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
```
@ -61,10 +67,18 @@ systemProp.socksProxyPort=9150
```
sdk.dir=/path/to/android-studio/sdk
```
1a. For building with a local router development build:
cd ../i2p.i2p
installer/resources/maven-dev-release.sh x // x is the build number, e.g. 6
cd back here
edit gradle.properties, add the build number x to I2P_PROPERTIES=0.9.xx-x
2. `gradle assembleDebug`
3. The APK will be placed in `i2p.android.base/app/build/outputs/apk`.
3. The APK files will be placed in `i2p.android.base/app/build/outputs/apk` subdirectories.
4. Install debug build on phone in USB debugging mode
adb install app/build/outputs/apk/free/debug/app-free-debug.apk
### Building with Android Studio
@ -107,8 +121,8 @@ systemProp.socksProxyPort=9150
signing.keyId=
signing.password=
signing.secretKeyRingFile=/path/to/secring.gpg
ossrhUsername=
ossrhPassword=
NEXUS_USERNAME=
NEXUS_PASSWORD=
```
2. `gradle :client:uploadArchives`

120
RELEASE-PROCESS.md Normal file
View File

@ -0,0 +1,120 @@
# Release Process
Note to all future maintainers: We have 4 channels that need to be updated in order to have a successful
Android release. Many of these channels are updated at different rates, and at times you must wait on a
third-party service to complete it's tasks before you may continue. When completing an Android release,
keep in mind that you must update 1) Maven 2) Google Play 3) f-droid.i2p.io and 4) F-Droid main
repository.
At the time of this revision, 2020/09/13, the main Android maintainer is idk. idk updates Maven, Google
Play, and f-droid.i2p.io, and nextl00p handles working with the F-Droid project to provide an I2P release
in their main repository.
NOTE: The docker container built by the Dockerfile in this repostory ensures that the Pre-requisites and
Dependencies are properly met by obtaining them from the Debian package in `oldoldstable` and pre-configuring
the override.properties that is used in the Docker container.
**>> Beginning of Docker-enabled Steps <<**
## Prerequirements
1. Ensure you have the deprecated maven ant tasks. ( https://maven.apache.org/ant-tasks/download.cgi )
2. It should exist at `~/.ant/lib/maven-ant-tasks-2.1.3.jar`
3. Ensure you have hamcrest-integration, hamcrest-library, hamcrest-core in the hamcrest.home directory.
4. Ensure junit 4.12 at least in junit.home, ensure the jar file is named `junit4.jar`.
5. Ensure you have the Mockito framework and accompanying documentation in your $JAVA_HOME
6. Ensure to have updated the changelog with the changes done.
7. Ensure that you are configured to build i2p.i2p with Java 8. On Debian it is easiest to set with
`update-java-alternatives --set java-8-openjdk-amd64` and picking Java 8. **TODO:** add instructions for non-Debian-based
systems.
8. Ensure that you have a Java 1.7 bootclasspath available. (See **Maven Central** step 2A.)
## Get all the dependencies ready
### Maven Central
1. Check out a clean copy of i2p.i2p at the correct release version. (Make a clean checkout)
2. Build the maven packages via `ant mavenCentral` where you end up with mavencentral-*.jar files in the
current directory.
2. **A)** I2P for Android requires a Java 1.7 bootclasspath, but the servlet jar requires Java 8. So, to do the builds:
- First set `javac.compilerargs=-bootclasspath /path/to/java/7/rt.jar:/path/to/java/7/jce.jar` in override.properties
- Build with `ant mavenCentral`
**>> End of Docker-enabled Steps for Maven <<**
3. Login to http://oss.sonatype.org for uploading the mavencentral-*.jar bundles.
4. In nexus, choose "Staging Upload" and upload all of the files with upload mode set to "Artifacts with POM".
When uploading the files to nexus, you *must* upload the pom.xml files, and all of their artifacts. For each
component, you will need to upload a *.jar, a *.jar.asc, a *sources.jar, a *sources.jar.asc, a javadoc.jar,
and a javadoc.jar.asc, and a pom.xml and a pom.xml.asc from the pkg-mavencentral directory during the "Upload
Artifacts with POM" operation. You will need to do this once for each component you upload to Nexus.
5. Under "Staging Repositories" ensure all where uploaded correctly, select them all and press "Release"
in the toolbar.
#### Example override.properties:
javac.version=1.7
javac.target=1.7
javac.source=1.8
javac.compilerargs=-bootclasspath /home/user/StudioProjects/java7bootclasspath/rt.jar:/home/user/StudioProjects/java7bootclasspath/jce.jar
javac.compilerargs7=-bootclasspath /home/user/StudioProjects/java7bootclasspath/rt.jar:/home/user/StudioProjects/java7bootclasspath/jce.jar
build.built-by=name
### Android Common Build
Using Docker: in order to use Docker to generate a new Android apk for release, you will
need to run the build twice, once for the mavenCentral jars, and once for the actual Android
app. After doing the Maven release, follow these steps in the i2p.android.base repository, and re-run
the `docker run` step described in `DOCKER.md`
1. Edit `routerjars/local.properties` to use the clean i2p.i2p copy.
2. Pull the latest translations with `tx pull -a` and commit them. (If you don't have the `tx` command,
do `pip install transifex-client` )
- If there are any new translations, `mtn add` them, and add them to `app/src/main/res/values/arrays.xml`
(two places, alphabetical order please)
3. Ensure that `signing.properties` contains the details of the release key. If you are using Docker, see
`DOCKER.md` to perform this step for Docker builds by editing `etc/docker.signing.properties` instead.
4. Edit `gradle.properties` to bump the I2P version.
5. Edit `app/build.gradle` to bump the Android version number.
6. Edit `CHANGELOG` to add the release and date.
7. If the helper has changed since the last release, edit
`lib/helper/gradle.properties` to bump the version.
8. `./gradlew clean assembleRelease`
### Libraries
1. `./gradlew :lib:client:uploadArchives`
2. If the helper version was changed and should be released: `./gradlew :lib:helper:uploadArchives`
3. Check on Sonatype that everything worked, and close/release.
## Release Packages
### F-Droid Guide
This guide is for f-droid.i2p.io, not for F-Droid's main repository. The repository keystore **and** the
config.py used to generate the repository are required to complete this process successfully.
1. Ensure you have the release keys, the keyfile must be placed at `~/.local/share/fdroidserver/keystore.jks`
2. If it's the first time, or you have reinstalled anything, ensure `path/to/fdroid/config.py` has correct
information.
3. Assuming you already have ran `./gradlew clean assembleRelease` from a earlier step, continue.
4. `cp app/build/outputs/apk/free/release/app-free-release.apk path/to/fdroid/repo/I2P-VERSION.apk`
5. Update `path/to/fdroid/metadata/net.i2p.android.txt` (The versions at the bottom of the file)
6. Run `fdroid update` from inside the fdroid path (install fdroid command via `pip install fdroidserver`)
7. Zip/tar the local fdroid repo and archive. `rm fdroid.tgz && tar czf fdroid.tgz archive/ repo/` from the
fdroid directory.
8. Push to download server and put in place. (via SSH for example, `scp fdroid.tgz download.i2p2.de:~/`)
9. On the server run `bin-fd/update-fdroid` and `sudo bin-fd/update-app i2p 0.9.40` (This ensures we use the
exact same apk file for the download page as in fdroid and gplay)
10. Check F-Droid repo works, and app works.
### Google Play and finishing up
1. Verify which files that are changed via `mtn ls cha`. It shouldn't be much more than those bellow this
line and possible translations (`mtn ls unk`).
2. Commit your release changes, `mtn ci gradle.properties lib/helper/gradle.properties app/build.gradle`
3. Push free and donate builds to Google Play via https://play.google.com/apps/publish/
4. Tag the new release. Example `mtn tag h: android-0.9.36`
5. Push the monotone changes. Make sure that they are there at the next git sync.
6. Update download page (version and hash, including F-Droid)

98
TODO
View File

@ -1,58 +1,128 @@
# Fixes
- Better twopane column widths
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
- Create tunnel wizard
<zzz> in the tunnel create wizard:
<zzz> 'this could be the full base 64 destination key, or an i2p url from your address book"
<zzz> 'host name' better than 'URL'. Technically speaking, a host name is not a URL
<zzz> hmm would be nice if they could be shared-client or have an option
<zzz> was setting up email tunnels
- I2PTunnel details
<zzz> on the i2ptunnel details, it lists both a 'target' and 'access point' for clients, with the same info
<zzz> generally we use 'target' for servers and 'access point' for clients, never both
- Browser
<zzzccc> Bug report: i2p browser treats 302 as an error
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page
- Console text change
<zzz> "download" and "upload" at the bottom of the status is a little misleading..
<zzz> maybe 'downstream bandwidth' or 'inbound usage' ?
- Fix visibility of advanced tunnel parameter changes
<zzz> when I change an advanced tunnel param e.g. length or variance, the change isn't displayed, I have to go back and forward again to see the change
# New UI fixes
- Addressbook action items are in tunnel overflow menu after moving from console to tunnels
- Material design:
- Style for addressbook headers
- Change console FAM icon when possible
<zzz> on the bottom right, the + and x icons might be better as a double-up arrow and double-down arrow?
- Use Material design for LongPressButton
- Highlight selected tunnel in two-pane mode
# Short-term
- Graceful shutdown option
<zzzccc> Request: graceful shutdown
- Remove peers page (HTML version)
- Add firewall help page showing current port settings
- GMP 6
- Fetch all JARs from Maven Central (ie. upload everything that I2P Android uses)
- 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
- Rewrite settings config handling
- Rewrite InitActivities
- I2PTunnel
- Improve tunnel list status indicators
- Icon overlay to indicate which tunnels are shared
- Or reorder / group tunnels?
- Show all messages somewhere
- Improve detail page, expose advanced settings
- Add edit page
- Bottom toolbar?
- Icons/header images for tunnel types on details page
- Setting to close when not on WiFi
- Progress feedback for addressbook subscriptions reload
- Display release notes directly on new router version
- Fill out help pages
- Fix navigation to specific settings pages
- Rewrite release notes to be release-specific
- Fix release notes UI, either make back button use clear or add buttons
- Notify user when autostart fails?
- 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
- Handle NetDB null cases (failed lookup of requested hash in detail page)
- Include GeoIP db for country info
- Maybe change router-off mechanic for various pages? Enable as they become available?
# Medium-term
- SQLite naming service backend to store addresses more effectively
- Leverage for name completion in e.g. browsers
- Create/edit tunnels while router is not running
- Separate out shared tunnel config
- Convey to users that one config controls all shared tunnels
- 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
- Bug report feature
- Replace peers page (native version)
- 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?
- Enable apps to specify when they don't need the router anymore
# Silent Store approval checks to confirm/implement
- Known Vulnerabilities
- Apps will be tested to ensure that they are not susceptible to known
publicly disclosed vulnerabilities. For example:
- Heartbleed
- Poodle
- MasterKey
- Common Path Traversal attacks
- Common SQL Injection attacks
- Network Security Protocols
- All Apps that require transmission of data from the App to a system that
does not exist on the device must use, at a minimum, TLS1.1 standards.
However, Blackphone would prefer the usage of TLS1.2.
- Apps must not use algorithms for cryptographic purposes that are considered
obsolete or outdated i.e. MD5, SHA1, RC4, DES, or any encryption algorithm
that is weaker than AES128.
- Transport Layer Protection
- All network communication should be encrypted
- Not vulnerable to SSl Strip
- Data Leakage
- No storage of sensitive data outside of application sandbox
- Files should not be created with MODE_WORLD_READABLE or MODE_WORLD_WRITABLE
- Copy & Paste will be evaluated on a case by case basis
- App logs should not contain sensitive information
- Authentication and Authorization
- Validate that authentication credentials are not stored on the device
- Must use an approved password-based key derivation function ie. PBKDF2, scrypt
- Data-at-rest Encryption
- Must use at a minimum AES128 with modes CCM or GCM
- Should not store the encryption key on the file system
- Permission Checks
- The App must function with all permissions disabled
- Apps must not hard crash if a permission is disabled
- Apps should ask users to enable permissions that are disabled if needed to
function properly and explain why the permission is necessary
- Privacy Policy
- Apps must have a privacy policy that details how customer data is used,
stored, shared, etc...
- Apps must be configured with the customer opted out by default
- App logs should not contain PII
- Error Handling
- Apps should follow best-practices for error handling and logging
# Long-term
- Reproducible builds
- Extract RouterService into a library
- Remote router support
- Implement a "router wrapper" that can represent a local or remote router
- Implement/use client APIs to talk to remote router

View File

@ -1,14 +1,19 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
repositories {
mavenLocal()
mavenCentral()
}
android {
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
compileSdkVersion 28
defaultConfig {
versionCode 4745225
versionName '0.9.17.1'
minSdkVersion 9
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
versionCode 4745264
versionName "$I2P_VERSION"
minSdkVersion 14
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
// For Espresso
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release
@ -16,10 +21,11 @@ android {
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
@ -30,40 +36,59 @@ android {
}
lintOptions {
abortOnError false
disable 'MissingDefaultResource'
}
packagingOptions {
exclude 'LICENSE.txt'
}
flavorDimensions 'tier'
productFlavors {
free {
dimension 'tier'
applicationId 'net.i2p.android'
}
donate {
dimension 'tier'
applicationId 'net.i2p.android.donate'
}
legacy {
dimension 'tier'
applicationId 'net.i2p.android.router'
}
}
buildToolsVersion '28.0.3'
}
dependencies {
compile project(':routerjars')
compile project(':client')
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0'
compile files('libs/androidplot-core-0.6.1.jar')
// Local dependencies
implementation project(':lib:client')
implementation project(':lib:helper')
implementation project(path: ':routerjars', configuration: 'routerjars')
// Android Support Repository dependencies
def supportVersion = '28.0.0'
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:recyclerview-v7:$supportVersion"
// Remote dependencies
implementation 'com.androidplot:androidplot-core:1.4.1'
implementation 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
implementation 'com.inkapplications.viewpageindicator:library:2.4.4'
implementation 'com.pnikosis:materialish-progress:1.7'
implementation "net.i2p:router:$I2P_VERSION"
implementation "net.i2p:i2p:$I2P_VERSION"
implementation "net.i2p.client:mstreaming:$I2P_VERSION"
implementation "net.i2p.client:streaming:$I2P_VERSION"
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
implementation 'org.sufficientlysecure:html-textview:3.1'
// Testing-only dependencies
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2') {
exclude group: 'com.android.support', module: 'support-annotations'
}
}
dependencyVerification {
verify = [
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab',
]
}
project.ext.i2pbase = '../i2p.i2p'
project.ext.i2pbase = "../i2p.i2p"
def Properties props = new Properties()
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
@ -89,11 +114,37 @@ task copyI2PResources(type: Copy) {
outputs.upToDateWhen { false }
into 'src/main/res'
into('drawable') {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/i2plogo.png')
}
into('raw') {
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
from(i2pbase + '/installer/resources/proxy') {
include { elem ->
elem.name.endsWith('.ht')
}
rename { String name ->
name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
}
filter { String line ->
// Remove links to routerconsole
def m = line =~ /127.0.0.1:7657/
if (m.getCount()) {
// Links around content
line = line.replaceAll(/<a href="http:\/\/127.0.0.1:7657[^>]*>(.+?)<\/a>/) { fullmatch, content ->
content
}
// Links in translation substitutions
line = line.replaceAll(/"<a href=\\"http:\/\/127.0.0.1:7657[^>]*>", "<\/a>"/, '"", ""')
}
// Remove "Configuration - Help - Addressbook" heading
def n = line =~ /Configuration.+Help.+Addressbook/
if (n.getCount())
""
else
line
}
}
from('../LICENSE.txt') { rename { 'license_app_txt' } }
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
from(i2pbase + '/licenses') {
@ -114,9 +165,7 @@ task copyI2PResources(type: Copy) {
]
}
rename { String name ->
String part = name.substring(8, name.lastIndexOf('.txt'))
String.format('license_%s_txt',
part.toLowerCase(Locale.US).replace('.', '_'))
name.toLowerCase(Locale.US).replace('-', '_').replace('.', '_')
}
}
from certificatesZip
@ -128,15 +177,15 @@ task copyI2PAssets(type: Copy) {
outputs.upToDateWhen { false }
into 'src/main/assets/themes/console'
into('images') {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/i2plogo.png')
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/inbound.png')
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/outbound.png')
}
into('light') {
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/light/console.css')
}
into('light/images') {
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/light/images/header.png')
}
}
@ -148,6 +197,7 @@ task cleanI2PResources(type: Delete) {
delete fileTree('src/main/res/raw') {
include 'blocklist_txt'
include 'hosts_txt'
include '*_ht'
include 'license_*'
include 'certificates_zip'
}

Binary file not shown.

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

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.i2p.android.router"
android:installLocation="auto">
android:installLocation="auto"
android:sharedUserId="net.i2p">
<uses-sdk xmlns:tools="http://schemas.android.com/tools"
tools:overrideLibrary="android.support.v14.preference" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- following two are for UPnP -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<!-- required for reliable core functionality on Android, see:
https://geti2p.net/en/docs/applications/embedding
heading: "Design for and Encourage Long Uptimes"
-->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:icon="@drawable/ic_launcher_itoopie"
@ -29,10 +42,11 @@
</receiver>
<activity
android:name=".MainActivity"
android:name="net.i2p.android.I2PActivity"
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name"
android:launchMode="singleTop">
<!-- Console filters -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -41,35 +55,48 @@
<action android:name="net.i2p.android.router.START_I2P" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- Addressbook filters -->
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
</activity>
<activity
android:name=".NewsActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P News"
android:parentActivityName=".MainActivity">
android:label="@string/label_news"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.help.HelpActivity"
android:label="Help"
android:parentActivityName=".MainActivity">
android:label="@string/menu_help"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.help.BrowserConfigActivity"
android:label="Browser Configuration"
android:parentActivityName=".MainActivity">
android:label="@string/label_browser_configuration"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".LicenseActivity"
android:label="I2P License Information"
android:label="@string/label_licenses"
android:parentActivityName="net.i2p.android.help.HelpActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -94,81 +121,61 @@
</activity>
<activity
android:name=".SettingsActivity"
android:label="I2P Settings"
android:parentActivityName=".MainActivity">
android:label="@string/menu_settings"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookSettingsActivity"
android:label="Addressbook Settings"
android:label="@string/label_addressbook"
android:launchMode="singleTop"
android:parentActivityName=".addressbook.AddressbookActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookActivity"
android:label="Addressbook"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable_addressbook" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".addressbook.AddressbookAddWizardActivity"
android:label="Add new Destination"
android:parentActivityName=".addressbook.AddressbookActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.addressbook.AddressbookActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
android:label="I2PTunnel"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelDetailActivity"
android:label="I2PTunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.preferences.EditTunnelActivity"
android:label="@string/edit_tunnel"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelDetailActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.i2ptunnel.TunnelDetailActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
android:label="Tunnel Creation Wizard"
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".log.LogActivity"
android:label="I2P Logs"
android:parentActivityName=".MainActivity">
android:label="@string/label_logs"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".log.LogDetailActivity"
android:label="Log Entry"
android:label="@string/log_entry"
android:parentActivityName=".log.LogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -176,29 +183,29 @@
</activity>
<activity
android:name=".stats.RateGraphActivity"
android:label="Rate Graph"
android:parentActivityName=".MainActivity">
android:label="@string/label_graphs"
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".stats.PeersActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P Peers and Transport Status"
android:label="@string/label_peers_status"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".netdb.NetDbActivity"
android:label="NetDB"
android:parentActivityName=".MainActivity">
android:parentActivityName="net.i2p.android.I2PActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
android:value="net.i2p.android.I2PActivity" />
</activity>
<activity
android:name=".netdb.NetDbDetailActivity"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.EepGet;
@ -83,7 +85,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
int semi = rv.indexOf(";");
if (semi > 0)
rv = rv.substring(0, semi);
return rv.toLowerCase();
return rv.toLowerCase(Locale.US);
}
/**

View File

@ -1,22 +1,48 @@
package net.i2p.android.apps;
import java.io.File;
import android.content.Context;
import net.i2p.android.router.NewsActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Notifications;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.crypto.SU3File;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.util.RFC822Date;
import net.i2p.router.news.NewsEntry;
import net.i2p.router.news.NewsMetadata;
import net.i2p.router.news.NewsXMLParser;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.Translate;
import net.i2p.util.ReusableGZIPInputStream;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.RFC822Date;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Date;
import java.util.List;
/**
* From router console, simplified since we don't deal with router versions
* or updates.
*
* As of 0.9.41, implements ClientApp to hang us off the ClientAppManager,
* so we can remove the static reference.
*/
public class NewsFetcher implements Runnable, EepGet.StatusListener {
public class NewsFetcher implements Runnable, EepGet.StatusListener, ClientApp {
private final Context mCtx;
private final RouterContext _context;
private final Notifications _notif;
private final Log _log;
@ -26,20 +52,18 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private boolean _invalidated;
private File _newsFile;
private File _tempFile;
private static NewsFetcher _instance;
private volatile boolean _isRunning = true;
private Thread _thread;
private final ClientAppManager _mgr;
private volatile ClientAppState _state = UNINITIALIZED;
public static final String APP_NAME = "NewsFetcher";
public static /*final */ NewsFetcher getInstance() {
return _instance;
}
/**
* As of 0.9.41, returns a new one every time. Only call once.
*/
public static /* final */ synchronized NewsFetcher getInstance(
RouterContext ctx, Notifications notif) {
if (_instance != null)
return _instance;
_instance = new NewsFetcher(ctx, notif);
return _instance;
Context context, RouterContext ctx, Notifications notif) {
return new NewsFetcher(context, ctx, notif);
}
private static final String NEWS_DIR = "docs";
@ -47,35 +71,39 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
private static final String TEMP_NEWS_FILE = "news.xml.temp";
/**
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
* We may be able to change it to psi.i2p in a future release after
* the hostname propagates.
* Changed in 0.9.11 to the b32 for psi.i2p, run by psi.
* We may be able to change it to psi.i2p in a future release after
* the hostname propagates.
*
* @since 0.7.14 not configurable
* @since 0.7.14 not configurable
*/
private static final String BACKUP_NEWS_URL = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.xml";
private static final String BACKUP_NEWS_URL_SU3 = "http://dn3tvalnjz432qkqsvpfdqrwpqkw3ye4n4i2uyfr4jexvo3sp5ka.b32.i2p/news/news.su3";
private static final String PROP_LAST_CHECKED = "router.newsLastChecked";
private static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
private static final String DEFAULT_REFRESH_FREQUENCY = 24*60*60*1000 + "";
private static final String DEFAULT_REFRESH_FREQUENCY = 24 * 60 * 60 * 1000 + "";
private static final String PROP_NEWS_URL = "router.newsURL";
private static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
public static final String DEFAULT_NEWS_URL_SU3 = "http://tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq.b32.i2p/news.su3";
private NewsFetcher(RouterContext ctx, Notifications notif) {
private NewsFetcher(Context context, RouterContext ctx, Notifications notif) {
mCtx = context;
_context = ctx;
_notif = notif;
_context.addShutdownTask(new Shutdown());
_log = ctx.logManager().getLog(NewsFetcher.class);
try {
String last = ctx.getProperty(PROP_LAST_CHECKED);
if (last != null)
_lastFetch = Long.parseLong(last);
} catch (NumberFormatException nfe) {}
} catch (NumberFormatException nfe) {
}
File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
// isn't already there on android
newsDir.mkdir();
_newsFile = new File(newsDir, NEWS_FILE);
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
updateLastFetched();
_mgr = ctx.clientAppManager();
changeState(INITIALIZED);
_mgr.register(this);
}
private void updateLastFetched() {
@ -94,27 +122,37 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public String status() {
StringBuilder buf = new StringBuilder(128);
long now = _context.clock().now();
if (_lastUpdated > 0) {
buf.append(Translate.getString("News last updated {0} ago.",
DataHelper.formatDuration2(now - _lastUpdated),
_context, "foo"))
.append('\n');
}
if (_lastFetch > _lastUpdated) {
buf.append(Translate.getString("News last checked {0} ago.",
DataHelper.formatDuration2(now - _lastFetch),
_context, "foo"));
}
return buf.toString();
StringBuilder buf = new StringBuilder(128);
long now = _context.clock().now();
if (_lastUpdated > 0) {
buf.append(mCtx.getString(R.string.news_last_updated,
DataHelper.formatDuration2(now - _lastUpdated)))
.append('\n');
}
if (_lastFetch > _lastUpdated) {
buf.append(mCtx.getString(R.string.news_last_checked,
DataHelper.formatDuration2(now - _lastFetch)));
}
return buf.toString();
}
private static final long INITIAL_DELAY = 5*60*1000;
private static final long RUN_DELAY = 30*60*1000;
// Runnable
private static final long INITIAL_DELAY = 5 * 60 * 1000;
private static final long RUN_DELAY = 30 * 60 * 1000;
public void run() {
_thread = Thread.currentThread();
changeState(RUNNING);
try {
run2();
} finally {
_mgr.unregister(this);
changeState(STOPPED);
}
}
private void run2() {
try {
Thread.sleep(INITIAL_DELAY);
} catch (InterruptedException ie) {
@ -137,7 +175,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
return true;
updateLastFetched();
String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
DEFAULT_REFRESH_FREQUENCY);
DEFAULT_REFRESH_FREQUENCY);
try {
long ms = Long.parseLong(freq);
if (ms <= 0)
@ -158,8 +196,9 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
/**
* Call this when changing news URLs to force an update next time the timer fires.
* @since 0.8.7
* Call this when changing news URLs to force an update next time the timer fires.
*
* @since 0.8.7
*/
void invalidateNews() {
_lastModified = null;
@ -167,7 +206,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public void fetchNews() {
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL);
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL_SU3);
String proxyHost = "127.0.0.1";
int proxyPort = 4444;
if (_tempFile.exists())
@ -183,7 +222,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} else {
// backup news location - always proxied
_tempFile.delete();
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL_SU3, true, null, _lastModified);
get.addStatusListener(this);
if (get.fetch())
_lastModified = get.getLastModified();
@ -193,25 +232,38 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
// EepGet.StatusListener
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
// ignore
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (_log.shouldLog(Log.INFO))
_log.info("News fetched from " + url + " with " + (alreadyTransferred+bytesTransferred));
_log.info("News fetched from " + url + " with " + (alreadyTransferred + bytesTransferred));
long now = _context.clock().now();
if (_tempFile.exists()) {
boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true);
if (_tempFile.exists() && _tempFile.length() > 0) {
File from;
if (url.endsWith(".su3")) {
try {
from = processSU3();
} catch (IOException ioe) {
_log.error("Failed to extract the news file", ioe);
_tempFile.delete();
return;
}
} else {
from = _tempFile;
}
boolean copied = FileUtil.rename(from, _newsFile);
_tempFile.delete();
if (copied) {
_lastUpdated = now;
_tempFile.delete();
// Notify user
_notif.notify("News Updated", "Touch to view latest I2P news",
_notif.notify(mCtx.getString(R.string.news_updated),
mCtx.getString(R.string.view_news),
NewsActivity.class);
} else {
if (_log.shouldLog(Log.ERROR))
@ -226,19 +278,218 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_context.router().saveConfig();
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (_log.shouldLog(Log.WARN))
_log.warn("Failed to fetch the news from " + url);
_tempFile.delete();
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
private class Shutdown implements Runnable {
public void run() {
_isRunning = false;
if (_thread != null)
_thread.interrupt();
public void headerReceived(String url, int attemptNum, String key, String val) {
}
public void attempting(String url) {
}
//
// SU3 handlers
//
/**
* Process the fetched su3 news file _tempFile.
* Handles 3 types of contained files: xml.gz (preferred), xml, and html (old format fake xml)
*
* @return the temp file contining the HTML-format news.xml
* @since 0.9.20
*/
private File processSU3() throws IOException {
SU3File su3 = new SU3File(_context, _tempFile);
// real xml, maybe gz, maybe not
File to1 = new File(_context.getTempDir(), "tmp-" + _context.random().nextInt() + ".xml");
// real xml
File to2 = new File(_context.getTempDir(), "tmp2-" + _context.random().nextInt() + ".xml");
try {
su3.verifyAndMigrate(to1);
int type = su3.getFileType();
if (su3.getContentType() != SU3File.CONTENT_NEWS)
throw new IOException("bad content type: " + su3.getContentType());
if (type == SU3File.TYPE_HTML)
return to1;
if (type != SU3File.TYPE_XML && type != SU3File.TYPE_XML_GZ)
throw new IOException("bad file type: " + type);
File xml;
if (type == SU3File.TYPE_XML_GZ) {
gunzip(to1, to2);
xml = to2;
to1.delete();
} else {
xml = to1;
}
NewsXMLParser parser = new NewsXMLParser(_context);
parser.parse(xml);
xml.delete();
NewsMetadata data = parser.getMetadata();
List<NewsEntry> entries = parser.getEntries();
String sudVersion = su3.getVersionString();
String signingKeyName = su3.getSignerString();
File to3 = new File(_context.getTempDir(), "tmp3-" + _context.random().nextInt() + ".xml");
outputOldNewsXML(data, entries, sudVersion, signingKeyName, to3);
return to3;
} finally {
to2.delete();
}
}
/**
* Gunzip the file
*
* @since 0.9.20
*/
private static void gunzip(File from, File to) throws IOException {
ReusableGZIPInputStream in = ReusableGZIPInputStream.acquire();
OutputStream out = null;
try {
in.initialize(new FileInputStream(from));
out = new SecureFileOutputStream(to);
byte buf[] = new byte[4096];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
ReusableGZIPInputStream.release(in);
}
}
/**
* Output in the old format.
*
* @since 0.9.20
*/
private void outputOldNewsXML(NewsMetadata data, List<NewsEntry> entries,
String sudVersion, String signingKeyName, File to) throws IOException {
NewsMetadata.Release latestRelease = data.releases.get(0);
Writer out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(to), "UTF-8"));
out.write("<!--\n");
// update metadata in old format
out.write("<i2p.release ");
if (latestRelease.i2pVersion != null)
out.write(" version=\"" + latestRelease.i2pVersion + '"');
if (latestRelease.minVersion != null)
out.write(" minVersion=\"" + latestRelease.minVersion + '"');
if (latestRelease.minJavaVersion != null)
out.write(" minJavaVersion=\"" + latestRelease.minJavaVersion + '"');
String su3Torrent = "";
String su2Torrent = "";
for (NewsMetadata.Update update : latestRelease.updates) {
if (update.torrent != null) {
if ("su3".equals(update.type))
su3Torrent = update.torrent;
else if ("su2".equals(update.type))
su2Torrent = update.torrent;
}
}
if (!su2Torrent.isEmpty())
out.write(" su2Torrent=\"" + su2Torrent + '"');
if (!su3Torrent.isEmpty())
out.write(" su3Torrent=\"" + su3Torrent + '"');
out.write("/>\n");
// su3 and feed metadata for debugging
out.write("** News version:\t" + DataHelper.stripHTML(sudVersion) + '\n');
out.write("** Signed by:\t" + signingKeyName + '\n');
out.write("** Feed:\t" + DataHelper.stripHTML(data.feedTitle) + '\n');
out.write("** Feed ID:\t" + DataHelper.stripHTML(data.feedID) + '\n');
out.write("** Feed Date:\t" + (new Date(data.feedUpdated)) + '\n');
out.write("-->\n");
if (entries == null)
return;
for (NewsEntry e : entries) {
if (e.title == null || e.content == null)
continue;
out.write("<!-- Entry Date: " + (new Date(e.updated)) + " -->\n");
out.write("<h3>");
out.write(e.title);
out.write("</h3>\n");
out.write(e.content);
out.write("\n\n");
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
}
}
////// begin ClientApp interface
/**
* @since 0.9.41
*/
public synchronized void startup() {
changeState(STARTING);
_thread = new I2PAppThread(this, "NewsFetcher", true);
_thread.start();
}
/**
* @since 0.9.41
*/
public synchronized void shutdown(String[] args) {
if (_state != RUNNING)
return;
changeState(STOPPING);
_isRunning = false;
if (_thread != null)
_thread.interrupt();
changeState(STOPPED);
}
/**
* @since 0.9.41
*/
public ClientAppState getState() {
return _state;
}
/**
* @since 0.9.41
*/
public String getName() {
return APP_NAME;
}
/**
* @since 0.9.41
*/
public String getDisplayName() {
return APP_NAME;
}
////// end ClientApp interface
////// begin ClientApp helpers
/**
* @since 0.9.41
*/
private void changeState(ClientAppState state) {
changeState(state, null);
}
/**
* @since 0.9.41
*/
private synchronized void changeState(ClientAppState state, Exception e) {
_state = state;
_mgr.notify(this, state, null, e);
}
////// end ClientApp helpers
}

View File

@ -1,18 +1,26 @@
package net.i2p.android.help;
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.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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;
private boolean isInstalled;
/**
* A browser that we don't know about.
*
@ -55,7 +63,7 @@ public class Browser implements Comparable<Browser> {
}
@Override
public int compareTo(Browser browser) {
public int compareTo(@NonNull Browser browser) {
// Sort order: supported -> unknown -> unsupported
int a = getOrder(this);
int b = getOrder(browser);
@ -79,4 +87,23 @@ public class Browser implements Comparable<Browser> {
} else
return 2;
}
public boolean isInstalled(Context context){
if (isInstalled) {
return true;
}
// 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 = context.getPackageManager();
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
for (ResolveInfo browser : installedBrowsers) {
if (browser.activityInfo.packageName.equals(packageName)) {
isInstalled = true;
break;
}
}
return isInstalled;
}
}

View File

@ -1,5 +1,6 @@
package net.i2p.android.help;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.ColorMatrix;
@ -11,6 +12,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.android.router.R;
@ -35,8 +37,8 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
}
}
public static interface OnBrowserSelectedListener {
public void onBrowserSelected(Browser browser);
public interface OnBrowserSelectedListener {
void onBrowserSelected(Browser browser);
}
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {
@ -71,11 +73,15 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
holder.mLabel.setText(browser.label);
if (browser.isKnown) {
if (browser.isRecommended && browser.isInstalled) {
if (browser.isRecommended && browser.isInstalled(mCtx)) {
holder.mStatus.setImageDrawable(
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
holder.mStatus.setVisibility(View.VISIBLE);
} else if (browser.isSupported && !browser.isInstalled) {
} else if (browser.isSupported && browser.isInstalled(mCtx)) {
holder.mStatus.setImageDrawable(
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
holder.mStatus.setVisibility(View.INVISIBLE);
} else if (browser.isSupported && !browser.isInstalled(mCtx)) {
holder.mStatus.setImageDrawable(
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
holder.mStatus.setOnClickListener(new View.OnClickListener() {
@ -84,11 +90,15 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
String uriMarket = "market://search?q=pname:" + browser.packageName;
Uri uri = Uri.parse(uriMarket);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
mCtx.startActivity(intent);
try {
mCtx.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(mCtx, R.string.no_market_app, Toast.LENGTH_LONG).show();
}
}
});
holder.mStatus.setVisibility(View.VISIBLE);
} else if (!browser.isSupported) {
} else if (browser.isInstalled(mCtx) && !browser.isSupported) {
// Make the icon gray-scale to show it is unsupported
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);

View File

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

View File

@ -19,12 +19,15 @@ import android.view.ViewGroup;
import net.i2p.android.router.R;
import net.i2p.android.router.util.BetterAsyncTaskLoader;
import net.i2p.android.widget.DividerItemDecoration;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.Map;
@ -55,6 +58,9 @@ public class BrowserListFragment extends Fragment implements
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
// use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
@ -102,12 +108,12 @@ public class BrowserListFragment extends Fragment implements
@Override
public List<Browser> loadInBackground() {
List<Browser> browsers = new ArrayList<Browser>();
Map<String, String> recommendedMap = new HashMap<String, String>();
List<Browser> browsers = new ArrayList<>();
Map<String, String> recommendedMap = new HashMap<>();
for (int i = 0; i < recommended.size(); i++) {
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
}
Map<String, String> supportedMap = new HashMap<String, String>();
Map<String, String> supportedMap = new HashMap<>();
for (int i = 0; i < supported.size(); i++) {
supportedMap.put(supported.get(i), supportedLabels.get(i));
}
@ -117,8 +123,9 @@ public class BrowserListFragment extends Fragment implements
intent.setData(Uri.parse("http://stats.i2p"));
final PackageManager pm = getContext().getPackageManager();
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
Set<ResolveInfo> installedBrowsers = new HashSet<>(pm.queryIntentActivities(intent, 0));
// Compare installed browsers to supported browsers
for (ResolveInfo browser : installedBrowsers) {
if (recommended.contains(browser.activityInfo.packageName)) {
browsers.add(new Browser(pm, browser, true, true));

View File

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

View File

@ -30,7 +30,7 @@ public class HelpHtmlFragment extends Fragment {
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);
text.setHtml(getArguments().getInt(ARG_HTML_FILE));
return scroller;
}
}

View File

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

View File

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

View File

@ -1,33 +1,50 @@
package net.i2p.android.i2ptunnel;
import java.util.List;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.FragmentUtils;
import net.i2p.app.ClientAppState;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.List;
public class TunnelDetailFragment extends Fragment {
public static final String TUNNEL_ID = "tunnel_id";
OnTunnelDeletedListener mCallback;
TunnelDetailListener mCallback;
private TunnelControllerGroup mGroup;
private TunnelEntry mTunnel;
private Toolbar mToolbar;
private ImageView mStatus;
public static TunnelDetailFragment newInstance(int tunnelId) {
TunnelDetailFragment f = new TunnelDetailFragment();
@ -38,8 +55,9 @@ public class TunnelDetailFragment extends Fragment {
}
// Container Activity must implement this interface
public interface OnTunnelDeletedListener {
public void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
public interface TunnelDetailListener {
void onEditTunnel(int tunnelId);
void onTunnelDeleted(int tunnelId, int numTunnelsLeft);
}
@Override
@ -48,36 +66,44 @@ public class TunnelDetailFragment extends Fragment {
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnTunnelDeletedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnTunnelDeletedListener");
}
mCallback = FragmentUtils.getParent(this, TunnelDetailListener.class);
if (mCallback == null)
throw new ClassCastException("Parent must implement TunnelDetailListener");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
String error;
List<TunnelController> controllers;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
controllers = mGroup == null ? null : mGroup.getControllers();
} catch (IllegalArgumentException iae) {
mGroup = null;
controllers = null;
error = iae.toString();
}
if (mGroup == null) {
// Show error
Toast.makeText(getActivity().getApplicationContext(),
error, Toast.LENGTH_LONG).show();
getActivity().finish();
} else if (getArguments().containsKey(TUNNEL_ID)) {
int tunnelId = getArguments().getInt(TUNNEL_ID);
mTunnel = new TunnelEntry(getActivity(),
mGroup.getControllers().get(tunnelId),
tunnelId);
try {
TunnelController controller = controllers.get(tunnelId);
mTunnel = new TunnelEntry(getActivity(), controller, tunnelId);
} catch (IndexOutOfBoundsException e) {
// Tunnel doesn't exist
Util.e("Could not load tunnel details", e);
Toast.makeText(getActivity().getApplicationContext(),
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
getActivity().finish();
}
}
}
@ -86,7 +112,21 @@ public class TunnelDetailFragment extends Fragment {
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_i2ptunnel_detail, container, false);
mToolbar = (Toolbar) v.findViewById(R.id.detail_toolbar);
mToolbar.inflateMenu(R.menu.fragment_i2ptunnel_detail_actions);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return onToolbarItemSelected(menuItem);
}
});
updateToolbar();
if (mTunnel != null) {
mStatus = (ImageView) v.findViewById(R.id.tunnel_status);
updateStatus();
ViewCompat.setTransitionName(mStatus, "status" + mTunnel.getId());
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(mTunnel.getName());
@ -96,14 +136,62 @@ public class TunnelDetailFragment extends Fragment {
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());
if (!mTunnel.getDetails().isEmpty()) {
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
View copyDetails = v.findViewById(R.id.tunnel_details_copy);
details.setText(mTunnel.getDetails());
if (!mTunnel.isClient()) {
copyDetails.setVisibility(View.VISIBLE);
copyDetails.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
copyToClipbardLegacy();
else
copyToClipboardHoneycomb();
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
targetIfacePort.setText(mTunnel.getTunnelLink(false));
Toast.makeText(getActivity(), R.string.address_copied_to_clipboard, Toast.LENGTH_SHORT).show();
}
});
}
}
View accessIfacePortItem = v.findViewById(R.id.tunnel_access_interface_port_item);
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
accessIfacePort.setText(mTunnel.getTunnelLink(false));
View accessIfaceOpen = v.findViewById(R.id.tunnel_access_open);
View targetIfacePortItem = v.findViewById(R.id.tunnel_target_interface_port_item);
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
View targetIfaceOpen = v.findViewById(R.id.tunnel_target_open);
switch (mTunnel.getInternalType()) {
case "httpbidirserver":
accessIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(accessIfaceOpen, true);
v.findViewById(R.id.icon_link_access).setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(targetIfaceOpen, false);
break;
case "streamrserver":
accessIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(accessIfaceOpen, true);
targetIfacePortItem.setVisibility(View.GONE);
break;
case "streamrclient":
accessIfacePortItem.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(targetIfaceOpen, false);
break;
default:
if (mTunnel.isClient()) {
accessIfacePort.setText(mTunnel.getClientLink(false));
setupOpen(accessIfaceOpen, true);
targetIfacePortItem.setVisibility(View.GONE);
} else {
accessIfacePortItem.setVisibility(View.GONE);
targetIfacePort.setText(mTunnel.getServerLink(false));
setupOpen(targetIfaceOpen, false);
}
}
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
autoStart.setChecked(mTunnel.startAutomatically());
@ -112,19 +200,56 @@ public class TunnelDetailFragment extends Fragment {
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_i2ptunnel_detail_actions, menu);
// Hide the edit action until we have an edit UI
menu.findItem(R.id.action_edit_tunnel).setVisible(false);
private void setupOpen(View open, final boolean client) {
if (mTunnel.isRunning() &&
(client ? mTunnel.isClientLinkValid() : mTunnel.isServerLinkValid())) {
open.setVisibility(View.VISIBLE);
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(client ? mTunnel.getClientLink(true) : mTunnel.getServerLink(true)));
try {
startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = mTunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getContext(),
R.string.no_market_app,
Toast.LENGTH_LONG).show();
}
}
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
}
}
});
} else
open.setVisibility(View.GONE);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
private void updateToolbar() {
Menu menu = mToolbar.getMenu();
MenuItem start = menu.findItem(R.id.action_start_tunnel);
MenuItem stop = menu.findItem(R.id.action_stop_tunnel);
if (mTunnel != null) {
if (mTunnel != null && mGroup != null &&
(mGroup.getState() == ClientAppState.STARTING ||
mGroup.getState() == ClientAppState.RUNNING)) {
boolean isStopped = mTunnel.getStatus() == TunnelEntry.NOT_RUNNING;
start.setVisible(isStopped);
@ -141,8 +266,18 @@ public class TunnelDetailFragment extends Fragment {
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
private void updateStatus() {
mStatus.setImageDrawable(mTunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
mStatus.setBackgroundDrawable(mTunnel.getStatusBackground());
else
mStatus.setBackground(mTunnel.getStatusBackground());
}
private boolean onToolbarItemSelected(MenuItem item) {
if (mTunnel == null)
return false;
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_start_tunnel:
@ -150,46 +285,96 @@ public class TunnelDetailFragment extends Fragment {
Toast.makeText(getActivity().getApplicationContext(),
getResources().getString(R.string.i2ptunnel_msg_tunnel_starting)
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// Reload the action bar to change the start/stop action
getActivity().supportInvalidateOptionsMenu();
// Reload the toolbar to change the start/stop action
updateToolbar();
// Update the status icon
updateStatus();
return true;
case R.id.action_stop_tunnel:
mTunnel.getController().stopTunnel();
Toast.makeText(getActivity().getApplicationContext(),
getResources().getString(R.string.i2ptunnel_msg_tunnel_stopping)
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// Reload the action bar to change the start/stop action
getActivity().supportInvalidateOptionsMenu();
// Reload the toolbar to change the start/stop action
updateToolbar();
// Update the status icon
updateStatus();
return true;
case R.id.action_edit_tunnel:
mCallback.onEditTunnel(mTunnel.getId());
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");
DialogFragment dg = DeleteTunnelDialogFragment.newInstance();
dg.show(getChildFragmentManager(), "delete_tunnel_dialog");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void onDeleteTunnel() {
List<String> msgs = TunnelUtil.deleteTunnel(
I2PAppContext.getGlobalContext(),
mGroup, mTunnel.getId(), null);
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
mCallback.onTunnelDeleted(mTunnel.getId(),
mGroup.getControllers().size());
}
private void copyToClipbardLegacy() {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mTunnel.getDetails());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void copyToClipboardHoneycomb() {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
mTunnel.getName(), mTunnel.getDetails());
clipboard.setPrimaryClip(clip);
}
public static class DeleteTunnelDialogFragment extends DialogFragment {
TunnelDetailFragment mListener;
public static DialogFragment newInstance() {
return new DeleteTunnelDialogFragment();
}
private void onAttachToParentFragment(Fragment fragment) {
// Verify that the host fragment implements the callback interface
try {
// Instantiate the TunnelDetailFragment so we can send events to the host
mListener = (TunnelDetailFragment) fragment;
} catch (ClassCastException e) {
// The fragment doesn't implement the interface, throw exception
throw new ClassCastException(fragment.toString()
+ " must be TunnelDetailFragment");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onAttachToParentFragment(getParentFragment());
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.i2ptunnel_delete_confirm_message)
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mListener.onDeleteTunnel();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
}

View File

@ -1,19 +1,29 @@
package net.i2p.android.i2ptunnel;
import java.util.List;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.Toast;
import net.i2p.android.i2ptunnel.util.TunnelConfig;
import net.i2p.I2PAppContext;
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* A single tunnel.
* Stored by the TunnelEntryAdapter.
*/
public class TunnelEntry {
public static final int RUNNING = 1;
public static final int STARTING = 2;
@ -24,18 +34,33 @@ public class TunnelEntry {
private final TunnelController mController;
private final int mId;
/**
* @param tcg non-null
* @return the new TunnelEntry, or null if there was an error.
*/
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());
TunnelEntry ret = null;
List<String> msgs = new ArrayList<>();
SaveTunnelTask task = new SaveTunnelTask(tcg, -1, cfg);
try {
msgs.addAll(task.execute().get());
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
ret = new TunnelEntry(ctx, cur, tunnelId);
} catch (InterruptedException e) {
Util.e("Interrupted while saving tunnel config", e);
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
} catch (ExecutionException e) {
Util.e("Error while saving tunnel config", e);
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
}
// 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);
return ret;
}
public TunnelEntry(Context context, TunnelController controller, int id) {
@ -250,7 +275,7 @@ public class TunnelEntry {
if (isClient())
details = getClientDestination();
else
details = "";
details = getDestHashBase32();
return details;
}
@ -260,6 +285,8 @@ public class TunnelEntry {
return mContext.getResources()
.getDrawable(R.drawable.ic_schedule_black_24dp);
case STARTING:
return mContext.getResources()
.getDrawable(R.drawable.ic_sync_black_24dp);
case RUNNING:
case NOT_RUNNING:
default:

View File

@ -1,94 +1,230 @@
package net.i2p.android.i2ptunnel;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
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 android.widget.Toast;
import net.i2p.android.router.R;
import net.i2p.android.util.FragmentUtils;
import java.util.ArrayList;
import java.util.List;
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
private final LayoutInflater mInflater;
/**
* Contains the List of TunnelEntries.
* There's two of these, one for client tunnels and
* one for server tunnels.
* Created by the TunnelListFragment.
*/
public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context mCtx;
private final boolean mClientTunnels;
private final TunnelListFragment.OnTunnelSelectedListener mListener;
private final FragmentUtils.TwoPaneProvider mTwoPane;
private List<TunnelEntry> mTunnels;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = -1;
public TunnelEntryAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
public void setData(List<TunnelEntry> tunnels) {
clear();
if (tunnels != null) {
for (TunnelEntry tunnel : tunnels) {
add(tunnel);
}
public static class TunnelViewHolder extends RecyclerView.ViewHolder {
public final ImageView status;
public final TextView name;
public final TextView description;
public final TextView interfacePort;
public TunnelViewHolder(View itemView) {
super(itemView);
status = (ImageView) itemView.findViewById(R.id.tunnel_status);
name = (TextView) itemView.findViewById(R.id.tunnel_name);
description = (TextView) itemView.findViewById(R.id.tunnel_description);
interfacePort = (TextView) itemView.findViewById(R.id.tunnel_interface_port);
}
}
public TunnelEntryAdapter(Context context, boolean clientTunnels,
TunnelListFragment.OnTunnelSelectedListener listener,
FragmentUtils.TwoPaneProvider twoPane) {
super();
mCtx = context;
mClientTunnels = clientTunnels;
mListener = listener;
mTwoPane = twoPane;
}
public void setTunnels(List<TunnelEntry> tunnels) {
mTunnels = tunnels;
notifyDataSetChanged();
}
public void addTunnel(TunnelEntry tunnel) {
if (mTunnels == null)
mTunnels = new ArrayList<TunnelEntry>();
boolean wasEmpty = mTunnels.isEmpty();
mTunnels.add(tunnel);
if (wasEmpty) {
notifyDataSetChanged();
} else {
notifyItemInserted(mTunnels.size() - 1);
}
}
public TunnelEntry getTunnel(int position) {
if (position < 0)
return null;
return mTunnels.get(position);
}
public void setActivatedPosition(int position) {
mActivatedPosition = position;
}
public int getActivatedPosition() {
return mActivatedPosition;
}
public void clearActivatedPosition() {
mActivatedPosition = -1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
final TunnelEntry tunnel = getItem(position);
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
status.setImageDrawable(tunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
status.setBackgroundDrawable(tunnel.getStatusBackground());
public int getItemViewType(int position) {
if (mTunnels == null)
return R.string.router_not_running;
else if (mTunnels.isEmpty())
return R.layout.listitem_empty;
else
status.setBackground(tunnel.getStatusBackground());
return R.layout.listitem_i2ptunnel;
}
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
name.setText(tunnel.getName());
// Create new views (invoked by the layout manager)
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int vt = viewType;
if (viewType == R.string.router_not_running)
vt = R.layout.listitem_empty;
TextView type = (TextView) v.findViewById(R.id.tunnel_description);
type.setText(tunnel.getDescription());
View v = LayoutInflater.from(parent.getContext())
.inflate(vt, parent, false);
switch (viewType) {
case R.layout.listitem_i2ptunnel:
return new TunnelViewHolder(v);
default:
return new SimpleViewHolder(v);
}
}
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
ifacePort.setText(tunnel.getTunnelLink(false));
private void setClipboard(Context context, String text) {
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("Copied Text", text);
clipboard.setPrimaryClip(clip);
}
}
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) {
View open = v.findViewById(R.id.tunnel_open);
open.setVisibility(View.VISIBLE);
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
try {
getContext().startActivity(i);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.install_recommended_app)
.setMessage(R.string.app_needed_for_this_tunnel_type)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Uri uri = tunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
getContext().startActivity(intent);
}
}
})
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
builder.show();
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case R.string.router_not_running:
((TextView) holder.itemView).setText(
mCtx.getString(R.string.i2ptunnel_not_initialized));
break;
case R.layout.listitem_empty:
((TextView) holder.itemView).setText(mClientTunnels ?
R.string.no_configured_client_tunnels :
R.string.no_configured_server_tunnels);
break;
case R.layout.listitem_i2ptunnel:
final TunnelViewHolder tvh = (TunnelViewHolder) holder;
final TunnelEntry tunnel = getTunnel(position);
tvh.status.setImageDrawable(tunnel.getStatusIcon());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
tvh.status.setBackgroundDrawable(tunnel.getStatusBackground());
else
tvh.status.setBackground(tunnel.getStatusBackground());
ViewCompat.setTransitionName(tvh.status,
"status" + tunnel.getId());
tvh.name.setText(tunnel.getName());
tvh.description.setText(tunnel.getDescription());
tvh.interfacePort.setText(tunnel.getTunnelLink(false));
tvh.itemView.setSelected(mTwoPane.isTwoPane() && position == mActivatedPosition);
tvh.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// TODO
// lint priority 8/10
// lint: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later
// javadocs: Note that unlike ListView, RecyclerView will not call this method again
// if the position of the item changes in the data set unless the item itself is invalidated
// or the new position cannot be determined.
// For this reason, you should only use the position parameter while acquiring
// the related data item inside this method and should not keep a copy of it.
// If you need the position of an item later on (e.g. in a click listener),
// use RecyclerView.ViewHolder.getAdapterPosition() which will have the updated adapter position.
int oldPosition = mActivatedPosition;
mActivatedPosition = position;
notifyItemChanged(oldPosition);
notifyItemChanged(position);
Pair<View, String> statusPair = Pair.create(
(View)tvh.status,
ViewCompat.getTransitionName(tvh.status));
Pair<View, String>[] pairs = new Pair[]{ statusPair};
mListener.onTunnelSelected(tunnel.getId(), pairs);
view.invalidate();
}
}
});
});
tvh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
//@Override
public boolean onLongClick(View view) {
setClipboard(mCtx, tunnel.getDestHashBase32());
Toast clipboardMessage = Toast.makeText(mCtx, R.string.copied_base32_system_notification_title, Toast. LENGTH_LONG);
clipboardMessage.setGravity(Gravity.TOP, 0, 0); //optional
clipboardMessage.show();
view.invalidate();
return true;
}
});
break;
default:
break;
}
return v;
}
// Return the size of the dataset (invoked by the layout manager)
@Override
public int getItemCount() {
if (mTunnels == null || mTunnels.isEmpty())
return 1;
return mTunnels.size();
}
}

View File

@ -1,20 +1,22 @@
package net.i2p.android.i2ptunnel;
import java.util.ArrayList;
import java.util.List;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import android.content.Context;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.util.Util;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.List;
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
private TunnelControllerGroup mGroup;
private boolean mClientTunnels;
private final TunnelControllerGroup mGroup;
private final boolean mClientTunnels;
private List<TunnelEntry> mData;
private Handler mHandler;
private final Handler mHandler;
private TunnelControllerMonitor mMonitor;
public TunnelEntryLoader(Context context, TunnelControllerGroup tcg, boolean clientTunnels) {
@ -26,7 +28,13 @@ public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
@Override
public List<TunnelEntry> loadInBackground() {
List<TunnelEntry> ret = new ArrayList<TunnelEntry>();
// Don't load tunnels if the router is not running
// TODO: in future we might be able to view and edit tunnels while router is not running
RouterContext routerContext = Util.getRouterContext();
if (routerContext == null)
return null;
List<TunnelEntry> ret = new ArrayList<>();
List<TunnelController> controllers = mGroup.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);

View File

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

View File

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

View File

@ -1,15 +1,18 @@
package net.i2p.android.i2ptunnel;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
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
@ -19,25 +22,51 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
@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() {
return FinishWizardDialogFragment.newInstance();
}
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();
public void onFinishWizard() {
Intent result = new Intent();
result.putExtra(TunnelsContainer.TUNNEL_WIZARD_DATA, mWizardModel.save());
setResult(Activity.RESULT_OK, result);
finish();
}
public static class FinishWizardDialogFragment extends DialogFragment {
TunnelWizardActivity mListener;
public static DialogFragment newInstance() {
return new FinishWizardDialogFragment();
}
public void onAttach(Context context) {
super.onAttach(context);
// Verify that the host fragment implements the callback interface
try {
// Instantiate the TunnelWizardActivity so we can send events to the host
mListener = (TunnelWizardActivity) context;
} catch (ClassCastException e) {
// The fragment doesn't implement the interface, throw exception
throw new ClassCastException(context.toString()
+ " must be TunnelWizardActivity");
}
};
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.i2ptunnel_wizard_submit_confirm_message)
.setPositiveButton(R.string.i2ptunnel_wizard_submit_confirm_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mListener.onFinishWizard();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,125 @@
package net.i2p.android.i2ptunnel.preferences;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.widget.Toast;
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.preferences.util.CustomPreferenceFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
protected static final String ARG_TUNNEL_ID = "tunnelId";
protected TunnelControllerGroup mGroup;
protected int mTunnelId;
@Override
public void onCreatePreferences(Bundle paramBundle, String s) {
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
} catch (IllegalArgumentException iae) {
mGroup = null;
error = iae.toString();
}
if (mGroup == null) {
Toast.makeText(getActivity().getApplicationContext(),
error, Toast.LENGTH_LONG).show();
getActivity().finish();
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
try {
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
} catch (IllegalArgumentException e) {
// Tunnel doesn't exist, or the tunnel config file could not be read
Util.e("Could not load tunnel details", e);
Toast.makeText(getActivity().getApplicationContext(),
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
getActivity().finish();
}
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
try {
loadPreferences();
} catch (IllegalArgumentException iae) {
// mGroup couldn't load its config file
Toast.makeText(getActivity().getApplicationContext(),
iae.toString(), Toast.LENGTH_LONG).show();
getActivity().finish();
}
}
}
@Override
public void onPause() {
super.onPause();
// Pre-Honeycomb: onPause() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
@Override
public void onStop() {
super.onStop();
// Honeycomb and above: onStop() is the last method guaranteed to be called.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
saveTunnel();
}
private void saveTunnel() {
if (mGroup != null) {
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
SaveTunnelTask task = new SaveTunnelTask(mGroup, mTunnelId, cfg);
try {
// TODO: There used to be a possible ANR here, because the underlying I2P code
// checks if the session is open as part of updating its config. We may need to save
// completely asynchronously (and ensure we do actually save before the app closes).
task.execute().get(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Util.e("Interrupted while saving tunnel config", e);
} catch (ExecutionException e) {
Util.e("Error while saving tunnel config", e);
} catch (CancellationException e) {
Util.e("Cancelled while saving tunnel config", e);
} catch (TimeoutException e) {
Util.e("Timed out while savomg tunnel config", e);
}
}
}
protected abstract void loadPreferences();
/**
* http://stackoverflow.com/a/20806812
*
* @param id the Preferences XML to load
* @param newParent the parent PreferenceGroup to add the new Preferences to.
*/
protected void addPreferencesFromResource(int id, PreferenceGroup newParent) {
PreferenceScreen screen = getPreferenceScreen();
int last = screen.getPreferenceCount();
addPreferencesFromResource(id);
while (screen.getPreferenceCount() > last) {
Preference p = screen.getPreference(last);
screen.removePreference(p); // decreases the preference count
newParent.addPreference(p);
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,32 @@
package net.i2p.android.i2ptunnel.util;
import android.os.AsyncTask;
import net.i2p.I2PAppContext;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.i2ptunnel.ui.TunnelConfig;
import java.util.List;
/**
* Save a TunnelConfig.
*
* This must be performed in a background thread, because the underlying I2P code calls
* InetAddress.getByName(), which will trigger a NetworkOnMainThreadException otherwise.
*/
public class SaveTunnelTask extends AsyncTask<Void, Void, List<String>> {
final TunnelControllerGroup mGroup;
final int mTunnelId;
final TunnelConfig mCfg;
public SaveTunnelTask(TunnelControllerGroup group, int tunnelId, TunnelConfig cfg) {
mGroup = group;
mTunnelId = tunnelId;
mCfg = cfg;
}
@Override
protected List<String> doInBackground(Void... voids) {
return TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, mCfg);
}
}

View File

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

View File

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

View File

@ -1,161 +1,28 @@
package net.i2p.android.i2ptunnel.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.android.wizard.model.Page;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.FileUtil;
import net.i2p.util.SecureFile;
import net.i2p.i2ptunnel.ui.GeneralHelper;
import net.i2p.i2ptunnel.ui.TunnelConfig;
public abstract class TunnelUtil {
public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) {
if (tunnel < 0) return null;
if (tcg == null) return null;
List<TunnelController> controllers = tcg.getControllers();
if (controllers.size() > tunnel)
return controllers.get(tunnel);
else
return null;
public class TunnelUtil extends GeneralHelper {
public static final String PREFERENCES_FILENAME_PREFIX = "tunnel.";
public TunnelUtil(I2PAppContext context, TunnelControllerGroup tcg) {
super(context, tcg);
}
public static List<String> saveTunnel(Context ctx,
TunnelControllerGroup tcg,
int tunnelId,
Properties config) {
// Get current tunnel controller
TunnelController cur = getController(tcg, tunnelId);
if (config == null) {
List<String> ret = new ArrayList<String>();
ret.add("Invalid params");
return ret;
}
if (cur == null) {
// creating new
cur = new TunnelController(config, "", true);
tcg.addController(cur);
if (cur.getStartOnLoad())
cur.startTunnelBackground();
} else {
cur.setConfig(config, "");
}
// Only modify other shared tunnels
// if the current tunnel is shared, and of supported type
if (Boolean.parseBoolean(cur.getSharedClient()) && isClient(cur.getType())) {
// all clients use the same I2CP session, and as such, use the same I2CP options
List<TunnelController> controllers = tcg.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelController c = controllers.get(i);
// Current tunnel modified by user, skip
if (c == cur) continue;
// Only modify this non-current tunnel
// if it belongs to a shared destination, and is of supported type
if (Boolean.parseBoolean(c.getSharedClient()) && isClient(c.getType())) {
Properties cOpt = c.getConfig("");
if (config.getProperty("option.inbound.quantity") != null)
cOpt.setProperty("option.inbound.quantity", config.getProperty("option.inbound.quantity"));
if (config.getProperty("option.outbound.quantity") != null)
cOpt.setProperty("option.outbound.quantity", config.getProperty("option.outbound.quantity"));
if (config.getProperty("option.inbound.length") != null)
cOpt.setProperty("option.inbound.length", config.getProperty("option.inbound.length"));
if (config.getProperty("option.outbound.length") != null)
cOpt.setProperty("option.outbound.length", config.getProperty("option.outbound.length"));
if (config.getProperty("option.inbound.lengthVariance") != null)
cOpt.setProperty("option.inbound.lengthVariance", config.getProperty("option.inbound.lengthVariance"));
if (config.getProperty("option.outbound.lengthVariance") != null)
cOpt.setProperty("option.outbound.lengthVariance", config.getProperty("option.outbound.lengthVariance"));
if (config.getProperty("option.inbound.backupQuantity") != null)
cOpt.setProperty("option.inbound.backupQuantity", config.getProperty("option.inbound.backupQuantity"));
if (config.getProperty("option.outbound.backupQuantity") != null)
cOpt.setProperty("option.outbound.backupQuantity", config.getProperty("option.outbound.backupQuantity"));
cOpt.setProperty("option.inbound.nickname", TunnelConfig.CLIENT_NICKNAME);
cOpt.setProperty("option.outbound.nickname", TunnelConfig.CLIENT_NICKNAME);
c.setConfig(cOpt, "");
}
}
}
return doSave(ctx, tcg);
}
/**
* Stop the tunnel, delete from config,
* rename the private key file if in the default directory
*/
public static List<String> deleteTunnel(Context ctx, TunnelControllerGroup tcg, int tunnelId) {
List<String> msgs;
TunnelController cur = getController(tcg, tunnelId);
if (cur == null) {
msgs = new ArrayList<String>();
msgs.add("Invalid tunnel number");
return msgs;
}
msgs = tcg.removeController(cur);
msgs.addAll(doSave(ctx, tcg));
// Rename private key file if it was a default name in
// the default directory, so it doesn't get reused when a new
// tunnel is created.
// Use configured file name if available, not the one from the form.
String pk = cur.getPrivKeyFile();
//if (pk == null)
// pk = _privKeyFile;
if (pk != null && pk.startsWith("i2ptunnel") && pk.endsWith("-privKeys.dat") &&
((!isClient(cur.getType())) || cur.getPersistentClientKey())) {
I2PAppContext context = I2PAppContext.getGlobalContext();
File pkf = new File(context.getConfigDir(), pk);
if (pkf.exists()) {
String name = cur.getName();
if (name == null) {
name = cur.getDescription();
if (name == null) {
name = cur.getType();
if (name == null)
name = Long.toString(context.clock().now());
}
}
name = "i2ptunnel-deleted-" + name.replace(' ', '_') + '-' + context.clock().now() + "-privkeys.dat";
File backupDir = new SecureFile(context.getConfigDir(), TunnelController.KEY_BACKUP_DIR);
File to;
if (backupDir.isDirectory() || backupDir.mkdir())
to = new File(backupDir, name);
else
to = new File(context.getConfigDir(), name);
boolean success = FileUtil.rename(pkf, to);
if (success)
msgs.add("Private key file " + pkf.getAbsolutePath() +
" renamed to " + to.getAbsolutePath());
}
}
return msgs;
}
private static List<String> doSave(Context ctx, TunnelControllerGroup tcg) {
List<String> rv = tcg.clearAllMessages();
try {
tcg.saveConfig();
rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_saved));
} catch (IOException ioe) {
Util.e("Failed to save config file", ioe);
rv.add(0, ctx.getResources().getString(R.string.i2ptunnel_msg_config_save_failed) + ": " + ioe.toString());
}
return rv;
public TunnelUtil(TunnelControllerGroup tcg) {
super(tcg);
}
/* General tunnel data for any type */
@ -192,50 +59,745 @@ public abstract class TunnelUtil {
public static String getTypeName(String type, Context context) {
Resources res = context.getResources();
if ("client".equals(type))
return res.getString(R.string.i2ptunnel_type_client);
else if ("httpclient".equals(type))
return res.getString(R.string.i2ptunnel_type_httpclient);
else if ("ircclient".equals(type))
return res.getString(R.string.i2ptunnel_type_ircclient);
else if ("server".equals(type))
return res.getString(R.string.i2ptunnel_type_server);
else if ("httpserver".equals(type))
return res.getString(R.string.i2ptunnel_type_httpserver);
else if ("sockstunnel".equals(type))
return res.getString(R.string.i2ptunnel_type_sockstunnel);
else if ("socksirctunnel".equals(type))
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
else if ("connectclient".equals(type))
return res.getString(R.string.i2ptunnel_type_connectclient);
else if ("ircserver".equals(type))
return res.getString(R.string.i2ptunnel_type_ircserver);
else if ("streamrclient".equals(type))
return res.getString(R.string.i2ptunnel_type_streamrclient);
else if ("streamrserver".equals(type))
return res.getString(R.string.i2ptunnel_type_streamrserver);
else if ("httpbidirserver".equals(type))
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
else
return type;
switch (type) {
case "client":
return res.getString(R.string.i2ptunnel_type_client);
case "httpclient":
return res.getString(R.string.i2ptunnel_type_httpclient);
case "ircclient":
return res.getString(R.string.i2ptunnel_type_ircclient);
case "server":
return res.getString(R.string.i2ptunnel_type_server);
case "httpserver":
return res.getString(R.string.i2ptunnel_type_httpserver);
case "sockstunnel":
return res.getString(R.string.i2ptunnel_type_sockstunnel);
case "socksirctunnel":
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
case "connectclient":
return res.getString(R.string.i2ptunnel_type_connectclient);
case "ircserver":
return res.getString(R.string.i2ptunnel_type_ircserver);
case "streamrclient":
return res.getString(R.string.i2ptunnel_type_streamrclient);
case "streamrserver":
return res.getString(R.string.i2ptunnel_type_streamrserver);
case "httpbidirserver":
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
default:
return type;
}
}
public static boolean isClient(String type) {
return ( ("client".equals(type)) ||
("httpclient".equals(type)) ||
("sockstunnel".equals(type)) ||
("socksirctunnel".equals(type)) ||
("connectclient".equals(type)) ||
("streamrclient".equals(type)) ||
("ircclient".equals(type)));
return TunnelController.isClient(type);
}
public static String getPrivateKeyFile(TunnelControllerGroup tcg, int tunnel) {
TunnelController tun = getController(tcg, tunnel);
if (tun != null && tun.getPrivKeyFile() != null)
return tun.getPrivKeyFile();
if (tunnel < 0)
tunnel = tcg == null ? 999 : tcg.getControllers().size();
return "i2ptunnel" + tunnel + "-privKeys.dat";
public static String getPreferencesFilename(int tunnel) {
return PREFERENCES_FILENAME_PREFIX + tunnel;
}
public static void writeTunnelToPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
new TunnelUtil(tcg).writeTunnelToPreferences(ctx, tunnel);
}
public void writeTunnelToPreferences(Context ctx, int tunnel) {
Resources res = ctx.getResources();
if (getController(tunnel) == null)
throw new IllegalArgumentException("Cannot write non-existent tunnel to Preferences");
// Get the current preferences for this tunnel
SharedPreferences preferences = ctx.getSharedPreferences(
getPreferencesFilename(tunnel), Context.MODE_PRIVATE);
// Clear all previous values
SharedPreferences.Editor ed = preferences.edit().clear();
// Load the tunnel config into the preferences
String type = getTunnelType(tunnel);
ed.putString(res.getString(R.string.TUNNEL_TYPE), type);
new TunnelToPreferences(ed, res, tunnel, type).runLogic();
ed.apply();
}
class TunnelToPreferences extends TunnelLogic {
final SharedPreferences.Editor ed;
final Resources res;
final int tunnel;
public TunnelToPreferences(SharedPreferences.Editor ed, Resources res, int tunnel, String type) {
super(type);
this.ed = ed;
this.res = res;
this.tunnel = tunnel;
}
@Override
protected void general() {
ed.putString(res.getString(R.string.TUNNEL_NAME), getTunnelName(tunnel));
ed.putString(res.getString(R.string.TUNNEL_DESCRIPTION), getTunnelDescription(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_START_ON_LOAD), shouldStartAutomatically(tunnel));
if (!isClient(mType) || getPersistentClientKey(tunnel))
ed.putString(res.getString(R.string.TUNNEL_PRIV_KEY_FILE), getPrivateKeyFile(tunnel));
}
@Override
protected void generalClient() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY), getPersistentClientKey(tunnel));
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr)
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
else
ed.putBoolean(res.getString(R.string.TUNNEL_SHARED_CLIENT), isSharedClient(tunnel));
}
@Override
protected void generalClientPort() {
ed.putInt(res.getString(R.string.TUNNEL_LISTEN_PORT), getClientPort(tunnel));
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (!isStreamr)
ed.putString(res.getString(R.string.TUNNEL_INTERFACE), getClientInterface(tunnel));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy)
ed.putString(res.getString(R.string.TUNNEL_PROXIES), getClientDestination(tunnel));
else
ed.putString(res.getString(R.string.TUNNEL_DEST), getClientDestination(tunnel));
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (isHttp)
ed.putString(res.getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES), getSslProxies(tunnel));
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
/* # TODO: See trac issue #2296
if (isStandardOrIrc)
ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
*/
}
@Override
protected void generalClientIrc() {
ed.putBoolean(res.getString(R.string.TUNNEL_IRCCLIENT_ENABLE_DCC), getDCC(tunnel));
}
@Override
protected void generalServerHttp() {
ed.putString(res.getString(R.string.TUNNEL_SPOOFED_HOST), getSpoofedHost(tunnel));
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
ed.putString(res.getString(R.string.TUNNEL_INTERFACE), getClientInterface(tunnel));
if (!isStreamr)
ed.putInt(res.getString(R.string.TUNNEL_LISTEN_PORT), getClientPort(tunnel));
}
@Override
protected void generalServerPort() {
ed.putInt(res.getString(R.string.TUNNEL_TARGET_PORT), getTargetPort(tunnel));
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
// # TODO: See trac issue #2296
//ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
}
}
@Override
protected void advanced() {
ed.putInt(res.getString(R.string.TUNNEL_OPT_LENGTH),
getTunnelDepth(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_VARIANCE),
getTunnelVariance(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
getTunnelBackupQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (!isStreamr)
ed.putString(res.getString(R.string.TUNNEL_OPT_PROFILE),
isInteractive(tunnel) ? "interactive" : "bulk");
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_CONNECT),
shouldDelayConnect(tunnel));
}
@Override
protected void advancedServer() {
//ed.putBoolean(res.getString(R.string.TUNNEL_OPT_ENCRYPT), getEncrypt(tunnel));
//ed.putString(res.getString(R.string.TUNNEL_OPT_ENCRYPT_KEY), getEncryptKey(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_ACCESS_MODE), getAccessMode(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_ACCESS_LIST), getAccessList(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_UNIQUE_LOCAL), getUniqueLocal(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_MULTIHOME), getMultihome(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_MINUTE), getLimitMinute(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_HOUR), getLimitHour(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_LIMIT_DAY), getLimitDay(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_MINUTE), getTotalMinute(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_HOUR), getTotalHour(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_TOTAL_DAY), getTotalDay(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_MAX_STREAMS), getMaxStreams(tunnel));
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp) {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_REJECT_INPROXY), getRejectInproxy(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_CHECK_TIME), getPostCheckTime(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_MAX), getPostMax(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_BAN_TIME), getPostBanTime(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_MAX), getPostTotalMax(tunnel));
ed.putInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_BAN_TIME), getPostTotalBanTime(tunnel));
}
}
@Override
protected void advancedIdle() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_REDUCE_IDLE),
getReduceOnIdle(tunnel, res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_REDUCE_QUANTITY),
getReduceCount(tunnel, res.getInteger(R.integer.DEFAULT_REDUCE_COUNT)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_REDUCE_TIME),
getReduceTime(tunnel, res.getInteger(R.integer.DEFAULT_REDUCE_TIME)));
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_OPEN), getDelayOpen(tunnel));
}
@Override
protected void advancedClient() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_CLOSE_IDLE),
getCloseOnIdle(tunnel, res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_CLOSE_TIME),
getCloseTime(tunnel, res.getInteger(R.integer.DEFAULT_CLOSE_TIME)));
ed.putBoolean(res.getString(R.string.TUNNEL_OTP_NEW_KEYS), getNewDest(tunnel));
}
@Override
protected void advancedClientHttp() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_UA), getAllowUserAgent(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_REFERER), getAllowReferer(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_ACCEPT), getAllowAccept(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_ALLOW_SSL), getAllowInternalSSL(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_JUMP_LIST), getJumpList(tunnel));
}
@Override
protected void advancedClientProxy() {
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_LOCAL_AUTH), !"false".equals(getProxyAuth(tunnel)));
ed.putString(res.getString(R.string.TUNNEL_OPT_LOCAL_USERNAME), "");
ed.putString(res.getString(R.string.TUNNEL_OPT_LOCAL_PASSWORD), "");
ed.putBoolean(res.getString(R.string.TUNNEL_OPT_OUTPROXY_AUTH), getOutproxyAuth(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_USERNAME), getOutproxyUsername(tunnel));
ed.putString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_PASSWORD), getOutproxyPassword(tunnel));
}
@Override
protected void advancedOther() {
ed.putInt(res.getString(R.string.TUNNEL_OPT_SIGTYPE), getSigType(tunnel, mType));
ed.putString(res.getString(R.string.TUNNEL_OPT_CUSTOM_OPTIONS), getCustomOptionsString(tunnel));
}
}
public static TunnelConfig createConfigFromPreferences(Context ctx, TunnelControllerGroup tcg, int tunnel) {
return new TunnelUtil(tcg).createConfigFromPreferences(ctx, tunnel);
}
public TunnelConfig createConfigFromPreferences(Context ctx, int tunnel) {
Resources res = ctx.getResources();
// Get the current preferences for this tunnel
SharedPreferences prefs = ctx.getSharedPreferences(
getPreferencesFilename(tunnel), Context.MODE_PRIVATE);
// Create the TunnelConfig
TunnelConfig cfg = new TunnelConfig();
// Update the TunnelConfig from the preferences
cfg.setType(prefs.getString(res.getString(R.string.TUNNEL_TYPE), null));
String type = cfg.getType();
new TunnelConfigFromPreferences(cfg, prefs, res, _group, tunnel, type).runLogic();
return cfg;
}
class TunnelConfigFromPreferences extends TunnelLogic {
final TunnelConfig cfg;
final SharedPreferences prefs;
final Resources res;
final TunnelControllerGroup tcg;
final int tunnel;
public TunnelConfigFromPreferences(TunnelConfig cfg, SharedPreferences prefs, Resources res,
TunnelControllerGroup tcg, int tunnel, String type) {
super(type);
this.cfg = cfg;
this.prefs = prefs;
this.res = res;
this.tcg = tcg;
this.tunnel = tunnel;
}
@Override
protected void general() {
cfg.setName(prefs.getString(res.getString(R.string.TUNNEL_NAME), null));
cfg.setDescription(prefs.getString(res.getString(R.string.TUNNEL_DESCRIPTION), null));
cfg.setStartOnLoad(prefs.getBoolean(res.getString(R.string.TUNNEL_START_ON_LOAD),
res.getBoolean(R.bool.DEFAULT_START_ON_LOAD)));
if (!isClient(mType) || prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)))
cfg.setPrivKeyFile(prefs.getString(res.getString(R.string.TUNNEL_PRIV_KEY_FILE),
getPrivateKeyFile(tcg, tunnel)));
}
@Override
protected void generalClient() {
// See advancedClient() for persistent key handling
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr)
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), null));
else
cfg.setShared(prefs.getBoolean(res.getString(R.string.TUNNEL_SHARED_CLIENT),
res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS)));
}
@Override
protected void generalClientPort() {
cfg.setPort(prefs.getInt(res.getString(R.string.TUNNEL_LISTEN_PORT), -1));
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setReachableBy(prefs.getString(res.getString(R.string.TUNNEL_INTERFACE), "127.0.0.1"));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy)
cfg.setProxyList(prefs.getString(res.getString(R.string.TUNNEL_PROXIES), null));
else
cfg.setTargetDestination(prefs.getString(res.getString(R.string.TUNNEL_DEST), null));
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (isHttp)
cfg.setSslProxies(prefs.getString(res.getString(R.string.TUNNEL_HTTPCLIENT_SSL_OUTPROXIES), null));
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
/* # TODO: See trac issue #2296
if (isStandardOrIrc)
cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
*/
}
@Override
protected void generalClientIrc() {
cfg.setDCC(prefs.getBoolean(res.getString(R.string.TUNNEL_IRCCLIENT_ENABLE_DCC), false));
}
@Override
protected void generalServerHttp() {
cfg.setSpoofedHost(prefs.getString(res.getString(R.string.TUNNEL_SPOOFED_HOST), null));
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
cfg.setReachableBy(prefs.getString(res.getString(R.string.TUNNEL_INTERFACE), "127.0.0.1"));
if (!isStreamr)
cfg.setPort(prefs.getInt(res.getString(R.string.TUNNEL_LISTEN_PORT), -1));
}
@Override
protected void generalServerPort() {
cfg.setTargetPort(prefs.getInt(res.getString(R.string.TUNNEL_TARGET_PORT), -1));
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), "127.0.0.1"));
// # TODO: See trac issue #2296
//cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
}
}
@Override
protected void advanced() {
cfg.setTunnelDepth(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LENGTH),
res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH)));
cfg.setTunnelVariance(prefs.getInt(res.getString(R.string.TUNNEL_OPT_VARIANCE),
res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE)));
cfg.setTunnelQuantity(prefs.getInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
cfg.setTunnelBackupQuantity(prefs.getInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setProfile(prefs.getString(res.getString(R.string.TUNNEL_OPT_PROFILE), "bulk"));
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setConnectDelay(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_CONNECT), false));
}
@Override
protected void advancedServer() {
//cfg.setEncrypt(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_ENCRYPT), false));
//cfg.setEncryptKey(prefs.getString(res.getString(R.string.TUNNEL_OPT_ENCRYPT_KEY), ""));
cfg.setAccessMode(prefs.getInt(res.getString(R.string.TUNNEL_OPT_ACCESS_MODE), 0));
cfg.setAccessList(prefs.getString(res.getString(R.string.TUNNEL_OPT_ACCESS_LIST), ""));
cfg.setUniqueLocal(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_UNIQUE_LOCAL), false));
cfg.setMultihome(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_MULTIHOME), false));
cfg.setLimitMinute(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_MINUTE), 0));
cfg.setLimitHour(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_HOUR), 0));
cfg.setLimitDay(prefs.getInt(res.getString(R.string.TUNNEL_OPT_LIMIT_DAY), 0));
cfg.setTotalMinute(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_MINUTE), 0));
cfg.setTotalHour(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_HOUR), 0));
cfg.setTotalDay(prefs.getInt(res.getString(R.string.TUNNEL_OPT_TOTAL_DAY), 0));
cfg.setMaxStreams(prefs.getInt(res.getString(R.string.TUNNEL_OPT_MAX_STREAMS), 0));
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp) {
cfg.setRejectInproxy(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_REJECT_INPROXY), false));
cfg.setPostCheckTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_CHECK_TIME), 0));
cfg.setPostMax(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_MAX), 0));
cfg.setPostBanTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_BAN_TIME), 0));
cfg.setPostTotalMax(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_MAX), 0));
cfg.setPostTotalBanTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_POST_TOTAL_BAN_TIME), 0));
}
}
@Override
protected void advancedIdle() {
cfg.setReduce(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_REDUCE_IDLE),
res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE)));
cfg.setReduceCount(prefs.getInt(res.getString(R.string.TUNNEL_OPT_REDUCE_QUANTITY),
res.getInteger(R.integer.DEFAULT_REDUCE_COUNT)));
cfg.setReduceTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_REDUCE_TIME),
res.getInteger(R.integer.DEFAULT_REDUCE_TIME)));
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setDelayOpen(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_DELAY_OPEN),
res.getBoolean(R.bool.DEFAULT_DELAY_OPEN)));
}
@Override
protected void advancedClient() {
cfg.setClose(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_CLOSE_IDLE),
res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE)));
cfg.setCloseTime(prefs.getInt(res.getString(R.string.TUNNEL_OPT_CLOSE_TIME),
res.getInteger(R.integer.DEFAULT_CLOSE_TIME)));
cfg.setNewDest(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_PERSISTENT_KEY),
res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY)) ? 2 :
prefs.getBoolean(res.getString(R.string.TUNNEL_OTP_NEW_KEYS), res.getBoolean(R.bool.DEFAULT_NEW_KEYS)) ? 1 : 0);
}
@Override
protected void advancedClientHttp() {
cfg.setAllowUserAgent(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_UA), false));
cfg.setAllowReferer(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_REFERER), false));
cfg.setAllowAccept(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_PASS_ACCEPT), false));
cfg.setAllowInternalSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_HTTPCLIENT_ALLOW_SSL), false));
cfg.setJumpList(prefs.getString(res.getString(R.string.TUNNEL_OPT_JUMP_LIST),
res.getString(R.string.DEFAULT_JUMP_LIST)));
}
@Override
protected void advancedClientProxy() {
cfg.setProxyAuth(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_LOCAL_AUTH), false) ? "digest" : "false");
String username = prefs.getString(res.getString(R.string.TUNNEL_OPT_LOCAL_USERNAME), "");
if (!username.isEmpty()) {
cfg.setProxyUsername(username);
cfg.setProxyPassword(prefs.getString(res.getString(R.string.TUNNEL_OPT_LOCAL_PASSWORD), ""));
}
cfg.setOutproxyAuth(prefs.getBoolean(res.getString(R.string.TUNNEL_OPT_OUTPROXY_AUTH), false));
cfg.setOutproxyUsername(prefs.getString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_USERNAME), ""));
cfg.setOutproxyPassword(prefs.getString(res.getString(R.string.TUNNEL_OPT_OUTPROXY_PASSWORD), ""));
}
@Override
protected void advancedOther() {
cfg.setSigType(Integer.toString(prefs.getInt(res.getString(R.string.TUNNEL_OPT_SIGTYPE),
res.getInteger(R.integer.DEFAULT_SIGTYPE))));
cfg.setCustomOptions(prefs.getString(res.getString(R.string.TUNNEL_OPT_CUSTOM_OPTIONS), null));
}
}
/**
* @param data non-null
*/
public static TunnelConfig createConfigFromWizard(
Context ctx, TunnelControllerGroup tcg, Bundle data) {
return new TunnelUtil(tcg).createConfigFromWizard(ctx, data);
}
/**
* @param data non-null
*/
public TunnelConfig createConfigFromWizard(Context ctx, Bundle data) {
// Get the Bundle keys
Resources res = ctx.getResources();
// Create the TunnelConfig
TunnelConfig cfg = new TunnelConfig();
// Update the TunnelConfig from the tunnel wizard settings
String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
String kType = res.getString(R.string.i2ptunnel_wizard_k_type);
try {
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
String type = getTypeFromName(typeName, ctx);
cfg.setType(type);
new TunnelConfigFromWizard(cfg, data, res, _group, type).runLogic();
} catch (NullPointerException ex) {
Util.e("Exception while trying to create config from wizard: "+ex.getMessage());
}
return cfg;
}
class TunnelConfigFromWizard extends TunnelLogic {
final TunnelConfig cfg;
final Bundle data;
final Resources res;
final TunnelControllerGroup tcg;
public TunnelConfigFromWizard(TunnelConfig cfg, Bundle data, Resources res,
TunnelControllerGroup tcg, String type) {
super(type);
this.cfg = cfg;
this.data = data;
this.res = res;
this.tcg = tcg;
}
@Override
protected void general() {
cfg.setName(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_name)).getString(Page.SIMPLE_DATA_KEY));
cfg.setDescription(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_desc)).getString(Page.SIMPLE_DATA_KEY));
cfg.setStartOnLoad(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_auto_start)).getBoolean(Page.SIMPLE_DATA_KEY));
if (!isClient(mType) || res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY))
cfg.setPrivKeyFile(getPrivateKeyFile(tcg, -1));
}
@Override
protected void generalClient() {
// See advancedClient() for persistent key handling
}
@Override
protected void generalClientStreamr(boolean isStreamr) {
if (isStreamr)
cfg.setTargetHost(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_target_host)).getString(Page.SIMPLE_DATA_KEY));
else
cfg.setShared(res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS));
// Only set default tunnel parameters if this is not going to be a shared tunnel
if (isStreamr || res.getBoolean(R.bool.DEFAULT_SHARED_CLIENTS)) {
cfg.setTunnelDepth(res.getInteger(R.integer.DEFAULT_TUNNEL_LENGTH));
cfg.setTunnelVariance(res.getInteger(R.integer.DEFAULT_TUNNEL_VARIANCE));
cfg.setTunnelQuantity(res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY));
cfg.setTunnelBackupQuantity(res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY));
}
}
@Override
protected void generalClientPort() {
cfg.setPort(Integer.parseInt(data.getBundle(
res.getString(R.string.i2ptunnel_wizard_k_binding_port)).getString(Page.SIMPLE_DATA_KEY)));
}
@Override
protected void generalClientPortStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setReachableBy(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_reachable_on)).getString(Page.SIMPLE_DATA_KEY));
}
@Override
protected void generalClientProxy(boolean isProxy) {
if (isProxy)
cfg.setProxyList(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_outproxies)).getString(Page.SIMPLE_DATA_KEY));
else
cfg.setTargetDestination(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_dest)).getString(Page.SIMPLE_DATA_KEY));
}
@Override
protected void generalClientProxyHttp(boolean isHttp) {
if (isHttp)
cfg.setSslProxies(null);
}
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
if (isStandardOrIrc)
cfg.setUseSSL(false);
}
@Override
protected void generalClientIrc() {
cfg.setDCC(false);
}
@Override
protected void generalServerHttp() {
cfg.setSpoofedHost(null);
}
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
cfg.setReachableBy(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_reachable_on)).getString(Page.SIMPLE_DATA_KEY));
if (!isStreamr)
cfg.setPort(Integer.parseInt(data.getBundle(
res.getString(R.string.i2ptunnel_wizard_k_binding_port)).getString(Page.SIMPLE_DATA_KEY)));
}
@Override
protected void generalServerPort() {
cfg.setTargetPort(Integer.parseInt(data.getBundle(
res.getString(R.string.i2ptunnel_wizard_k_target_port)).getString(Page.SIMPLE_DATA_KEY)));
}
@Override
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
cfg.setTargetHost(data.getBundle(res.getString(R.string.i2ptunnel_wizard_k_target_host)).getString(Page.SIMPLE_DATA_KEY));
cfg.setUseSSL(false);
}
}
@Override
protected void advanced() {
// Tunnel parameters handled in generalClientStreamr()
}
@Override
protected void advancedStreamr(boolean isStreamr) {
if (!isStreamr)
cfg.setProfile("bulk");
}
@Override
protected void advancedServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setConnectDelay(false);
}
@Override
protected void advancedServer() {
cfg.setEncrypt(false);
cfg.setAccessMode(0);
cfg.setUniqueLocal(false);
cfg.setMultihome(false);
cfg.setLimitMinute(0);
cfg.setLimitHour(0);
cfg.setLimitDay(0);
cfg.setTotalMinute(0);
cfg.setTotalHour(0);
cfg.setTotalDay(0);
cfg.setMaxStreams(0);
}
@Override
protected void advancedServerHttp(boolean isHttp) {
if (isHttp) {
cfg.setRejectInproxy(false);
cfg.setPostCheckTime(0);
cfg.setPostMax(0);
cfg.setPostBanTime(0);
cfg.setPostTotalMax(0);
cfg.setPostTotalBanTime(0);
}
}
@Override
protected void advancedIdle() {
cfg.setReduce(res.getBoolean(R.bool.DEFAULT_REDUCE_ON_IDLE));
cfg.setReduceCount(res.getInteger(R.integer.DEFAULT_REDUCE_COUNT));
cfg.setReduceTime(res.getInteger(R.integer.DEFAULT_REDUCE_TIME));
}
@Override
protected void advancedIdleServerOrStreamrClient(boolean isServerOrStreamrClient) {
if (!isServerOrStreamrClient)
cfg.setDelayOpen(res.getBoolean(R.bool.DEFAULT_DELAY_OPEN));
}
@Override
protected void advancedClient() {
cfg.setClose(res.getBoolean(R.bool.DEFAULT_CLOSE_ON_IDLE));
cfg.setCloseTime(res.getInteger(R.integer.DEFAULT_CLOSE_TIME));
cfg.setNewDest(res.getBoolean(R.bool.DEFAULT_PERSISTENT_KEY) ? 2 :
res.getBoolean(R.bool.DEFAULT_NEW_KEYS) ? 1 : 0);
}
@Override
protected void advancedClientHttp() {
cfg.setAllowUserAgent(false);
cfg.setAllowReferer(false);
cfg.setAllowAccept(false);
cfg.setAllowInternalSSL(false);
cfg.setJumpList(res.getString(R.string.DEFAULT_JUMP_LIST));
}
@Override
protected void advancedClientProxy() {
cfg.setProxyAuth("false");
cfg.setOutproxyAuth(false);
}
@Override
protected void advancedOther() {
cfg.setSigType(Integer.toString(res.getInteger(R.integer.DEFAULT_SIGTYPE)));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,139 @@
package net.i2p.android.router;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu.OnFloatingActionsMenuUpdateListener;
import net.i2p.android.router.dialog.AboutDialog;
import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.router.log.LogActivity;
import net.i2p.android.router.netdb.NetDbActivity;
import net.i2p.android.router.stats.PeersActivity;
import net.i2p.android.router.stats.RateGraphActivity;
import net.i2p.android.router.util.Util;
public class ConsoleContainer extends Fragment {
MainFragment mMainFragment = null;
FloatingActionsMenu mConsoleMenu;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.container_console, container, false);
// Start with the home view
if (savedInstanceState == null && getChildFragmentManager().findFragmentById(R.id.main_fragment) == null) {
mMainFragment = new MainFragment();
mMainFragment.setArguments(getActivity().getIntent().getExtras());
getChildFragmentManager().beginTransaction()
.add(R.id.main_fragment, mMainFragment).commit();
}
mConsoleMenu = (FloatingActionsMenu) v.findViewById(R.id.console_action_menu);
// update visibility based on router state
mConsoleMenu.setOnFloatingActionsMenuUpdateListener(new OnFloatingActionsMenuUpdateListener() {
public void onMenuExpanded() {
// this is called after the animation starts, sadly
setMenuVisibility();
}
public void onMenuCollapsed() {
// call it here too so the expand animation isn't glitchy as often
setMenuVisibility();
}
});
mConsoleMenu.findViewById(R.id.action_news).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent news = new Intent(getActivity(), NewsActivity.class);
startActivity(news);
}
});
mConsoleMenu.findViewById(R.id.action_logs).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent log = new Intent(getActivity(), LogActivity.class);
startActivity(log);
}
});
mConsoleMenu.findViewById(R.id.action_graphs).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent graphs = new Intent(getActivity(), RateGraphActivity.class);
startActivity(graphs);
}
});
// mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// Intent peers = new Intent(getActivity(), PeersActivity.class);
// startActivity(peers);
// }
// });
mConsoleMenu.findViewById(R.id.action_netdb).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent netdb = new Intent(getActivity(), NetDbActivity.class);
startActivity(netdb);
}
});
setMenuVisibility();
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.activity_main_actions, menu);
}
private void setMenuVisibility() {
boolean routerRunning = Util.getRouterContext() != null;
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_graphs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
if (getActivity() != null) {
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean("i2pandroid.main.showStats", false);
// mConsoleMenu.findViewById(R.id.action_peers).setVisibility(
// advanced && routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
advanced && routerRunning ? View.VISIBLE : View.GONE);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_about:
AboutDialog dialog = new AboutDialog();
dialog.show(getFragmentManager(), "about");
return true;
case R.id.menu_help_release_notes:
TextResourceDialog rDdialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
rDdialog.setArguments(args);
rDdialog.show(getFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

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

View File

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

View File

@ -1,5 +1,9 @@
package net.i2p.android.router;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import net.i2p.android.router.util.Util;
import net.i2p.router.CommSystemFacade;
import net.i2p.router.NetworkDatabaseFacade;
import net.i2p.router.Router;
@ -8,40 +12,12 @@ import net.i2p.router.TunnelManagerFacade;
import net.i2p.router.peermanager.ProfileOrganizer;
import net.i2p.router.transport.FIFOBandwidthLimiter;
import net.i2p.stat.StatManager;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
public class I2PFragmentBase extends Fragment {
private boolean mOnActivityCreated;
RouterContextProvider mCallback;
public static final String PREF_INSTALLED_VERSION = "app.version";
public interface RouterContextUser {
public void onRouterBind();
}
// Container Activity must implement this interface
public interface RouterContextProvider {
public RouterContext getRouterContext();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (RouterContextProvider) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement RouterContextProvider");
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@ -64,7 +40,7 @@ public class I2PFragmentBase extends Fragment {
public void onRouterConnectionNotReady() {}
protected RouterContext getRouterContext() {
return mCallback.getRouterContext();
return Util.getRouterContext();
}
protected Router getRouter() {

View File

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

View File

@ -5,7 +5,7 @@ import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
public class LicenseFragment extends ListFragment {
@ -28,7 +28,7 @@ public class LicenseFragment extends ListFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, names));
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
}
@Override

View File

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

View File

@ -1,26 +1,40 @@
package net.i2p.android.router;
import android.app.Activity;
import android.graphics.Color;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.util.AndroidRuntimeException;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
import net.i2p.android.I2PActivityBase;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.router.dialog.FirstStartDialog;
import net.i2p.android.router.dialog.VersionDialog;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.LongToggleButton;
@ -31,10 +45,8 @@ import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.router.RouterContext;
import net.i2p.router.TunnelPoolSettings;
import net.i2p.util.Translate;
import java.text.Collator;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -46,9 +58,24 @@ public class MainFragment extends I2PFragmentBase {
private Runnable _updater;
private Runnable _oneShotUpdate;
private String _savedStatus;
private boolean _keep = true;
private boolean _startPressed = false;
private ImageView mConsoleLights;
private LongToggleButton mOnOffButton;
private LinearLayout vGracefulButtons;
private ScrollView mScrollView;
private View vStatusContainer;
private ImageView vNetStatusLevel;
private TextView vNetStatusText;
private View vNonNetStatus;
private TextView vUptime;
private TextView vActive;
private TextView vKnown;
private TableLayout vTunnels;
private LinearLayout vAdvStatus;
private TextView vAdvStatusText;
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
private static final String PREF_CONFIGURE_BATTERY = "app.dialog.configureBattery";
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";
@ -57,10 +84,28 @@ public class MainFragment extends I2PFragmentBase {
// Container Activity must implement this interface
public interface RouterControlListener {
public boolean shouldShowOnOff();
public boolean shouldBeOn();
public void onStartRouterClicked();
public boolean onStopRouterClicked();
boolean shouldShowOnOff();
boolean shouldBeOn();
void onStartRouterClicked();
boolean onStopRouterClicked();
/**
* @since 0.9.19
*/
boolean isGracefulShutdownInProgress();
/**
* @since 0.9.19
*/
boolean onGracefulShutdownClicked();
/**
* @since 0.9.19
*/
boolean onCancelGracefulShutdownClicked();
}
@Override
@ -85,15 +130,14 @@ public class MainFragment extends I2PFragmentBase {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Init stuff here so settings work.
if(savedInstanceState != null) {
if (savedInstanceState != null) {
lastRouterState = savedInstanceState.getParcelable("lastState");
String saved = savedInstanceState.getString("status");
if(saved != null) {
if (saved != null) {
_savedStatus = saved;
}
}
_keep = true;
_handler = new Handler();
_updater = new Updater();
_oneShotUpdate = new OneShotUpdate();
@ -104,24 +148,54 @@ public class MainFragment extends I2PFragmentBase {
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, container, false);
final ImageView lightImage = (ImageView) v.findViewById(R.id.main_lights);
lightImage.setImageResource(R.drawable.routerlogo_0);
mConsoleLights = (ImageView) v.findViewById(R.id.console_lights);
mOnOffButton = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
vGracefulButtons = (LinearLayout) v.findViewById(R.id.router_graceful_buttons);
mScrollView = (ScrollView) v.findViewById(R.id.main_scrollview);
vStatusContainer = v.findViewById(R.id.status_container);
vNetStatusLevel = (ImageView) v.findViewById(R.id.console_net_status_level);
vNetStatusText = (TextView) v.findViewById(R.id.console_net_status_text);
vNonNetStatus = v.findViewById(R.id.console_non_net_status_container);
vUptime = (TextView) v.findViewById(R.id.console_uptime);
vActive = (TextView) v.findViewById(R.id.console_active);
vKnown = (TextView) v.findViewById(R.id.console_known);
vTunnels = (TableLayout) v.findViewById(R.id.main_tunnels);
vAdvStatus = (LinearLayout) v.findViewById(R.id.console_advanced_status);
vAdvStatusText = (TextView) v.findViewById(R.id.console_advanced_status_text);
LongToggleButton b = (LongToggleButton) v.findViewById(R.id.router_onoff_button);
b.setOnLongClickListener(new View.OnLongClickListener() {
updateState(lastRouterState);
mOnOffButton.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
boolean on = ((ToggleButton) view).isChecked();
if (on) {
_startPressed = true;
mCallback.onStartRouterClicked();
updateOneShot();
checkFirstStart();
} else {
if(mCallback.onStopRouterClicked()) {
} else if (mCallback.onGracefulShutdownClicked())
updateOneShot();
return true;
}
});
Button gb = (Button) v.findViewById(R.id.button_shutdown_now);
gb.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress())
if (mCallback.onStopRouterClicked())
updateOneShot();
return true;
}
});
gb = (Button) v.findViewById(R.id.button_cancel_graceful);
gb.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (mCallback.isGracefulShutdownInProgress())
if (mCallback.onCancelGracefulShutdownClicked())
updateOneShot();
}
}
return true;
}
});
@ -134,19 +208,46 @@ public class MainFragment extends I2PFragmentBase {
super.onStart();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
if(_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.main_status_text);
if (_savedStatus != null) {
TextView tv = (TextView) getActivity().findViewById(R.id.console_advanced_status_text);
tv.setText(_savedStatus);
}
checkDialog();
_handler.postDelayed(_updater, 100);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getActivity());
IntentFilter filter = new IntentFilter();
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_NOTIFICATION);
filter.addAction(RouterService.LOCAL_BROADCAST_STATE_CHANGED);
lbm.registerReceiver(onStateChange, filter);
lbm.sendBroadcast(new Intent(RouterService.LOCAL_BROADCAST_REQUEST_STATE));
}
private State lastRouterState;
private BroadcastReceiver onStateChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
State state = intent.getParcelableExtra(RouterService.LOCAL_BROADCAST_EXTRA_STATE);
if (lastRouterState == null || lastRouterState != state) {
updateState(state);
// If we have stopped, clear the status info immediately
if (Util.isStopped(state)) {
updateOneShot();
}
lastRouterState = state;
}
}
};
@Override
public void onStop() {
super.onStop();
_handler.removeCallbacks(_updater);
_handler.removeCallbacks(_oneShotUpdate);
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onStateChange);
}
@Override
@ -157,9 +258,10 @@ public class MainFragment extends I2PFragmentBase {
@Override
public void onSaveInstanceState(Bundle outState) {
if(_savedStatus != null) {
if (lastRouterState != null)
outState.putParcelable("lastState", lastRouterState);
if (_savedStatus != null)
outState.putString("status", _savedStatus);
}
super.onSaveInstanceState(outState);
}
@ -171,7 +273,12 @@ public class MainFragment extends I2PFragmentBase {
public void run() {
updateVisibility();
updateStatus();
try {
updateStatus();
} catch (NullPointerException npe) {
// RouterContext wasn't quite ready
Util.w("Status was updated before RouterContext was ready", npe);
}
}
}
@ -180,10 +287,16 @@ public class MainFragment extends I2PFragmentBase {
private int counter;
private final int delay = 1000;
private final int toloop = delay / 500;
public void run() {
updateVisibility();
if(counter++ % toloop == 0) {
updateStatus();
if (counter++ % toloop == 0) {
try {
updateStatus();
} catch (NullPointerException npe) {
// RouterContext wasn't quite ready
Util.w("Status was updated before RouterContext was ready", npe);
}
}
//_handler.postDelayed(this, 2500);
_handler.postDelayed(this, delay);
@ -192,101 +305,94 @@ public class MainFragment extends I2PFragmentBase {
private void updateVisibility() {
boolean showOnOff = mCallback.shouldShowOnOff();
ToggleButton b = (ToggleButton) getActivity().findViewById(R.id.router_onoff_button);
b.setVisibility(showOnOff ? View.VISIBLE : View.INVISIBLE);
mOnOffButton.setVisibility(showOnOff ? View.VISIBLE : View.GONE);
boolean isOn = mCallback.shouldBeOn();
b.setChecked(isOn);
mOnOffButton.setChecked(isOn);
if (showOnOff && !isOn) {
// Sometimes the final state message from the RouterService
// is not received. Ensure that the state image is correct.
// TODO: Fix the race between RouterService shutdown and
// IRouterState unbinding.
updateState(State.INIT);
}
}
public boolean onBackPressed() {
RouterContext ctx = getRouterContext();
// RouterService svc = _routerService; Which is better to use?!
_keep = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
Util.d("*********************************************************");
Util.d("Back pressed, Keep? " + _keep);
Util.d("*********************************************************");
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
if(!_keep) {
Thread t = new Thread(new KillMe());
t.start();
}
}
private class KillMe implements Runnable {
public void run() {
Util.d("*********************************************************");
Util.d("KillMe started!");
Util.d("*********************************************************");
try {
Thread.sleep(500); // is 500ms long enough?
} catch(InterruptedException ex) {
boolean isGraceful = mCallback.isGracefulShutdownInProgress();
vGracefulButtons.setVisibility(isGraceful ? View.VISIBLE : View.GONE);
if (isOn && isGraceful) {
RouterContext ctx = getRouterContext();
if (ctx != null) {
TextView tv = (TextView) vGracefulButtons.findViewById(R.id.router_graceful_status);
long ms = ctx.router().getShutdownTimeRemaining();
if (ms > 1000) {
tv.setText(getActivity().getResources().getString(R.string.button_router_graceful,
DataHelper.formatDuration(ms)));
} else {
tv.setText(getActivity().getString(R.string.notification_status_stopping));
}
}
System.exit(0);
}
}
public void updateState(State newState) {
final ImageView lightImage = (ImageView) getView().findViewById(R.id.main_lights);
/**
* Changes the logo based on the state.
*/
private void updateState(State newState) {
if (newState == State.INIT ||
newState == State.STOPPED ||
newState == State.MANUAL_STOPPED ||
newState == State.MANUAL_QUITTED ||
newState == State.NETWORK_STOPPED) {
lightImage.setImageResource(R.drawable.routerlogo_0);
mConsoleLights.setImageResource(R.drawable.routerlogo_0);
} else if (newState == State.STARTING ||
newState == State.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);
mConsoleLights.setImageResource(R.drawable.routerlogo_1);
} else if (newState == State.RUNNING ||
newState == State.GRACEFUL_SHUTDOWN) {
mConsoleLights.setImageResource(R.drawable.routerlogo_2);
} else if (newState == State.ACTIVE) {
lightImage.setImageResource(R.drawable.routerlogo_3);
mConsoleLights.setImageResource(R.drawable.routerlogo_3);
} else if (newState == State.WAITING) {
lightImage.setImageResource(R.drawable.routerlogo_4);
mConsoleLights.setImageResource(R.drawable.routerlogo_4);
} // Ignore unknown states.
}
private void updateStatus() {
RouterContext ctx = getRouterContext();
ScrollView sv = (ScrollView) getActivity().findViewById(R.id.main_scrollview);
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
if(!Connectivity.isConnected(getActivity())) {
if (!Connectivity.isConnected(getActivity())) {
// Manually set state, RouterService won't be running
updateState(State.WAITING);
vStatusText.setText("No Internet connection is available");
vStatus.setVisibility(View.VISIBLE);
sv.setVisibility(View.VISIBLE);
} else if(ctx != null) {
if(_startPressed) {
_startPressed = false;
vNetStatusText.setText(R.string.no_internet);
vStatusContainer.setVisibility(View.VISIBLE);
vNonNetStatus.setVisibility(View.GONE);
} else if (lastRouterState != null &&
!Util.isStopping(lastRouterState) &&
!Util.isStopped(lastRouterState) &&
ctx != null) {
Util.NetStatus netStatus = Util.getNetStatus(getActivity(), ctx);
switch (netStatus.level) {
case ERROR:
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_error_red_24dp));
vNetStatusLevel.setVisibility(View.VISIBLE);
break;
case WARN:
vNetStatusLevel.setImageDrawable(getResources().getDrawable(R.drawable.ic_warning_amber_24dp));
vNetStatusLevel.setVisibility(View.VISIBLE);
break;
case INFO:
default:
vNetStatusLevel.setVisibility(View.GONE);
}
vNetStatusText.setText(getString(R.string.settings_label_network) + ": " + netStatus.status);
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
int active = ctx.commSystem().countActivePeers();
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
vUptime.setText(uptime);
vActive.setText(Integer.toString(active));
vKnown.setText(Integer.toString(known));
// Load running tunnels
loadDestinations(ctx);
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SHOW_STATS, false)) {
short reach = ctx.commSystem().getReachabilityStatus();
int active = ctx.commSystem().countActivePeers();
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
int inEx = ctx.tunnelManager().getFreeTunnelCount();
int outEx = ctx.tunnelManager().getOutboundTunnelCount();
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
@ -295,80 +401,53 @@ public class MainFragment extends I2PFragmentBase {
double dLag = ctx.statManager().getRate("jobQueue.jobLag").getRate(60000).getAverageValue();
String jobLag = DataHelper.formatDuration((long) dLag);
String msgDelay = DataHelper.formatDuration(ctx.throttle().getMessageDelay());
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
String netstatus;
if (reach == net.i2p.router.CommSystemFacade.STATUS_DIFFERENT) {
netstatus = "Symmetric NAT";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_HOSED) {
netstatus = "Port Failure";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_OK) {
netstatus = "OK";
} else if (reach == net.i2p.router.CommSystemFacade.STATUS_REJECT_UNSOLICITED) {
netstatus = "Firewalled";
} else {
netstatus = "Unknown";
}
String tunnelStatus = ctx.throttle().getTunnelStatus();
//ctx.commSystem().getReachabilityStatus();
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
// control total width
DecimalFormat fmt;
if(inBW >= 1000 || outBW >= 1000) {
fmt = new DecimalFormat("#0");
} else if(inBW >= 100 || outBW >= 100) {
fmt = new DecimalFormat("#0.0");
} else {
fmt = new DecimalFormat("#0.00");
}
double kBytesIn = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes() / 1024;
double kBytesOut = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes() / 1024;
// control total width
DecimalFormat kBfmt;
if(kBytesIn >= 1000 || kBytesOut >= 1000) {
kBfmt = new DecimalFormat("#0");
} else if(kBytesIn >= 100 || kBytesOut >= 100) {
kBfmt = new DecimalFormat("#0.0");
} else {
kBfmt = new DecimalFormat("#0.00");
}
String status =
"Network: " + netstatus
+ "\nPeers active/known: " + active + " / " + known
+ "\nExploratory Tunnels in/out: " + inEx + " / " + outEx
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
"Exploratory Tunnels in/out: " + inEx + " / " + outEx
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
// Need to see if we have the participation option set to on.
// I thought there was a router method for that? I guess not! WHY NOT?
// It would be easier if we had a number to test status.
String participate = "\nParticipation: " + tunnelStatus +" (" + part + ")";
String participate = "\nParticipation: " + tunnelStatus + " (" + part + ")";
String details =
"\nBandwidth in/out: " + fmt.format(inBW) + " / " + fmt.format(outBW) + " KBps"
+ "\nData usage in/out: " + kBfmt.format(kBytesIn) + " / " + kBfmt.format(kBytesOut) + " KB"
+ "\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
"\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
+ "\nJob Lag: " + jobLag
+ "\nMsg Delay: " + msgDelay
+ "\nUptime: " + uptime;
+ "\nMsg Delay: " + msgDelay;
_savedStatus = status + participate + details;
vStatusText.setText(_savedStatus);
vStatus.setVisibility(View.VISIBLE);
vAdvStatusText.setText(_savedStatus);
vAdvStatus.setVisibility(View.VISIBLE);
} else {
vStatus.setVisibility(View.GONE);
vAdvStatus.setVisibility(View.GONE);
}
sv.setVisibility(View.VISIBLE);
vStatusContainer.setVisibility(View.VISIBLE);
vNonNetStatus.setVisibility(View.VISIBLE);
// Usage stats in bottom toolbar
double inBw = ctx.bandwidthLimiter().getReceiveBps();
double outBw = ctx.bandwidthLimiter().getSendBps();
double inData = ctx.bandwidthLimiter().getTotalAllocatedInboundBytes();
double outData = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes();
((TextView) getActivity().findViewById(R.id.console_download_stats)).setText(
Util.formatSpeed(inBw) + "Bps / " + Util.formatSize(inData) + "B");
((TextView) getActivity().findViewById(R.id.console_upload_stats)).setText(
Util.formatSpeed(outBw) + "Bps / " + Util.formatSize(outData) + "B");
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.VISIBLE);
} else {
// network but no router context
vStatusText.setText("Not running");
sv.setVisibility(View.INVISIBLE);
vStatusContainer.setVisibility(View.GONE);
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.INVISIBLE);
updateState(State.STOPPED);
/**
* **
* RouterService svc = _routerService; String status = "connected? "
@ -382,21 +461,24 @@ public class MainFragment extends I2PFragmentBase {
* "null" : svc.canManualStart()) + "\ncan stop? " + (svc == null ?
* "null" : svc.canManualStop()); tv.setText(status);
* tv.setVisibility(View.VISIBLE);
***
***
*/
}
}
/**
* Based on net.i2p.router.web.SummaryHelper.getDestinations()
*
* @param ctx The RouterContext
*/
private void loadDestinations(RouterContext ctx) {
TableLayout dests = (TableLayout) getView().findViewById(R.id.main_tunnels);
dests.removeAllViews();
vTunnels.removeAllViews();
List<Destination> clients = new ArrayList<Destination>(ctx.clientManager().listClients());
if (!clients.isEmpty()) {
List<Destination> clients = null;
if (ctx.clientManager() != null)
clients = new ArrayList<Destination>(ctx.clientManager().listClients());
if (clients != null && !clients.isEmpty()) {
Collections.sort(clients, new AlphaComparator(ctx));
for (Destination client : clients) {
String name = getName(ctx, client);
@ -438,25 +520,29 @@ public class MainFragment extends I2PFragmentBase {
type.setBackgroundResource(R.drawable.tunnel_yellow);
}
dests.addView(dest);
vTunnels.addView(dest);
}
} else {
TableRow empty = new TableRow(getActivity());
TextView emptyText = new TextView(getActivity());
emptyText.setText(R.string.no_client_tunnels_running);
emptyText.setText(R.string.no_tunnels_running);
empty.addView(emptyText);
dests.addView(empty);
vTunnels.addView(empty);
}
}
/** compare translated nicknames - put "shared clients" first in the sort */
private static final String SHARED_CLIENTS = "shared clients";
/**
* compare translated nicknames - put "shared clients" first in the sort
*/
private class AlphaComparator implements Comparator<Destination> {
private String xsc;
private RouterContext _ctx;
private final String xsc;
private final RouterContext _ctx;
public AlphaComparator(RouterContext ctx) {
_ctx = ctx;
xsc = _(ctx, "shared clients");
xsc = _t(ctx, SHARED_CLIENTS);
}
public int compare(Destination lhs, Destination rhs) {
@ -470,34 +556,133 @@ public class MainFragment extends I2PFragmentBase {
}
}
/** translate here so collation works above */
/**
* translate here so collation works above
*/
private String getName(RouterContext ctx, Destination d) {
TunnelPoolSettings in = ctx.tunnelManager().getInboundSettings(d.calculateHash());
String name = (in != null ? in.getDestinationNickname() : null);
if (name == null) {
TunnelPoolSettings out = ctx.tunnelManager().getOutboundSettings(d.calculateHash());
name = (out != null ? out.getDestinationNickname() : null);
if (name == null)
name = d.calculateHash().toBase64().substring(0,6);
else
name = _(ctx, name);
} else {
name = _(ctx, name);
}
if (name == null)
name = d.calculateHash().toBase64().substring(0, 6);
else
name = _t(ctx, name);
return name;
}
private String _(RouterContext ctx, String s) {
return Translate.getString(s, ctx, "net.i2p.router.web.messages");
private String _t(RouterContext ctx, String s) {
if (SHARED_CLIENTS.equals(s))
return getString(R.string.shared_clients);
else
return s;
}
private void checkDialog() {
I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
if (configureBrowser) {
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
ab.setPref(PREF_CONFIGURE_BROWSER, false);
final I2PActivityBase ab = (I2PActivityBase) getActivity();
String language = PreferenceManager.getDefaultSharedPreferences(ab).getString(
getString(R.string.PREF_LANGUAGE), null
);
if (language == null) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
// avoid ISE caused by fragment detachment ticket #2631
final String languages[] = getResources().getStringArray(R.array.languages);
b.setTitle(R.string.choose_language)
.setItems(R.array.language_names, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Save the language choice
String language = languages[which];
PreferenceManager.getDefaultSharedPreferences(getActivity())
.edit()
.putString(getString(R.string.PREF_LANGUAGE), language)
.apply();
// Close the dialog
dialog.dismiss();
// Broadcast the change to RouterService just in case the router is running
Intent intent = new Intent(RouterService.LOCAL_BROADCAST_LOCALE_CHANGED);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
// Update the parent
ab.notifyLocaleChanged();
// Run checkDialog() again to show the next dialog
// (if the change doesn't restart the Activity)
checkDialog();
}
})
.setCancelable(false)
.show();
} else if (ab.getPref(PREF_CONFIGURE_BROWSER, true)) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_browser_title)
.setMessage(R.string.configure_browser_for_i2p)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
ab.setPref(PREF_CONFIGURE_BROWSER, false);
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
checkDialog();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
ab.setPref(PREF_CONFIGURE_BROWSER, false);
checkDialog();
}
})
.show();
} else if (ab.getPref(PREF_CONFIGURE_BATTERY, true)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
// only for Marshmallow and newer versions
final Intent intent = new Intent();
final Context mContext = ab.getApplicationContext();
String packageName = mContext.getPackageName();
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_no_doze_title);
b.setMessage(R.string.configure_no_doze);
b.setCancelable(false);
b.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
String packageName = mContext.getPackageName();
ab.setPref(PREF_CONFIGURE_BATTERY, false);
dialog.dismiss();
// Simply do not re-attempt a battery optimization after the first time,
// even if an error occurs. http://trac.i2p2.i2p/ticket/2783
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivity(intent);
} catch (ActivityNotFoundException activityNotFound) {
ab.setPref(PREF_CONFIGURE_BATTERY, false);
} catch (AndroidRuntimeException activityNotFound) {
ab.setPref(PREF_CONFIGURE_BATTERY, false);
}
}
});
b.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
ab.setPref(PREF_CONFIGURE_BATTERY, false);
}
});
b.show();
}
} else {
ab.setPref(PREF_CONFIGURE_BATTERY, false);
}
}
/*VersionDialog dialog = new VersionDialog();
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
@ -522,9 +707,11 @@ public class MainFragment extends I2PFragmentBase {
private void checkFirstStart() {
I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
if (firstStart) {
// Check ab.isFinishing() because DialogFragment.show() will throw IllegalStateException if
// called after ab.onSaveInstanceState().
if (firstStart && !ab.isFinishing()) {
FirstStartDialog dialog = new FirstStartDialog();
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
dialog.show(ab.getSupportFragmentManager(), "firststart");
ab.setPref(PREF_FIRST_START, false);
}
}

View File

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

View File

@ -13,6 +13,7 @@ import android.widget.TextView;
import net.i2p.android.apps.NewsFetcher;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -33,12 +34,16 @@ public class NewsFragment extends I2PFragmentBase {
@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);
RouterContext ctx = getRouterContext();
if (ctx != null) {
NewsFetcher nf = (NewsFetcher) ctx.clientAppManager().getRegisteredApp(NewsFetcher.APP_NAME);
if (nf != null) {
// Always update the status
// This is the news last updated/checked text at the bottom
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,15 @@
package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
@ -18,25 +21,51 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
@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() {
return FinishWizardDialogFragment.newInstance();
}
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();
public void onFinishWizard() {
Intent result = new Intent();
result.putExtra(AddressbookContainer.ADD_WIZARD_DATA, mWizardModel.save());
setResult(Activity.RESULT_OK, result);
finish();
}
public static class FinishWizardDialogFragment extends DialogFragment {
AddressbookAddWizardActivity mListener;
public static DialogFragment newInstance() {
return new FinishWizardDialogFragment();
}
public void onAttach(Context context) {
super.onAttach(context);
// Verify that the host fragment implements the callback interface
try {
// Instantiate the AddressbookAddWizardActivity so we can send events to the host
mListener = (AddressbookAddWizardActivity) context;
} catch (ClassCastException e) {
// The fragment doesn't implement the interface, throw exception
throw new ClassCastException(context.toString()
+ " must be AddressbookAddWizardActivity");
}
};
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage("Add to private addressbook?")
.setPositiveButton("Add",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mListener.onFinishWizard();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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