Compare commits

..

216 Commits

Author SHA1 Message Date
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
364 changed files with 6125 additions and 2098 deletions

View File

@ -24,6 +24,7 @@ _jsp\.java$
/classes/
# Android-specific ignores
^lib/client/libs
^routerjars/libs
local.properties
signing.properties
@ -35,7 +36,6 @@ signing.properties
.*.iws
#Gradle
^.gradle
build
# I2P-specific ignores

View File

@ -1,18 +1,18 @@
[main]
host = https://www.transifex.com
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
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 = 30
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

View File

@ -1,9 +1,70 @@
0.9.20
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

View File

@ -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:
```
@ -107,8 +113,8 @@ systemProp.socksProxyPort=9150
signing.keyId=
signing.password=
signing.secretKeyRingFile=/path/to/secring.gpg
ossrhUsername=
ossrhPassword=
NEXUS_USERNAME=
NEXUS_PASSWORD=
```
2. `gradle :client:uploadArchives`

57
RELEASE-PROCESS.md Normal file
View File

@ -0,0 +1,57 @@
# Release Process
## Prerequirements
1. Ensure you got 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 got 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`.
## 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.
3. Login to http://oss.sonatype.org for uploading the mavencentral-*.jar bundles.
4. In nexus, choose "Staging Upload" and upload all of the bundles with upload mode set to "Artifact Bundle"
5. Under "Staging Repositories" ensure all where uploaded correctly, select them all and press "Release" in the toolbar.
## Android Common Build
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` )
3. Ensure that `signing.properties` contains the details of the release key.
4. Edit `gradle.properties` to bump the I2P version.
5. Edit `app/build.gradle` to bump the Android version number.
6. If the helper has changed since the last release, edit
`lib/helper/gradle.properties` to bump the version.
7. `./gradlew clean assembleRelease`
### Steps to take if the android helper is updated and should be released
1. `./gradlew :lib:client:uploadArchives`
2. If the helper version was changed: `./gradlew :lib:helper:uploadArchives`
3. Check on Sonatype that everything worked, and close/release.
## F-Droid Guide
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

33
TODO
View File

@ -6,45 +6,74 @@
- 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
- 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
- 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
@ -92,6 +121,8 @@
# 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,13 +1,11 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
android {
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION as String
compileSdkVersion 28
defaultConfig {
versionCode 4745229
versionName '0.9.19.1'
minSdkVersion 9
versionCode 4745248
versionName "$I2P_VERSION"
minSdkVersion 14
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
// For Espresso
@ -19,10 +17,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'
}
@ -33,64 +32,56 @@ 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 {
// Local dependencies
compile project(':routerjars')
compile project(':client')
implementation project(':lib:client')
implementation project(':lib:helper')
implementation project(path: ':routerjars', configuration: 'routerjars')
// Android Support Repository dependencies
compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:recyclerview-v7:22.2.0'
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
compile 'net.i2p.android.ext:floatingactionbutton:1.9.0'
compile files('libs/androidplot-core-0.6.1.jar')
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4'
}
compile 'com.pnikosis:materialish-progress:1.5'
compile 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
compile ('com.mcxiaoke.viewpagerindicator:library:2.4.1') {
exclude group: 'com.android.support', module: 'support-v4'
}
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.android.ext:floatingactionbutton:1.10.1'
implementation 'org.sufficientlysecure:html-textview:3.1'
// Testing-only dependencies
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
}
}
dependencyVerification {
verify = [
'com.android.support:support-v4:7bb6e40a18774aa2595e4d8f9fe0ae14e61670f71a1279272fb0b79b8be71180',
'com.android.support:appcompat-v7:2d5867698410b41f75140c91d6c1e58da74ae0f97baf6e0bdd1f7cc1017ceb2c',
'com.android.support:recyclerview-v7:3a8da14585fa1c81f06e7cef4d93a7641f0323d8f984ff9a7bd7a6e416b46888',
'net.i2p.android.ext:floatingactionbutton:b41eae5fe6be599e3fade00273521b0914f2e199d5f04c50fa34cfe935347f76',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
'com.eowise:recyclerview-stickyheaders:7b236da49b33b840e9ba6e7e4182218d1a2d9047236fdbc3ca947352f9b0883b',
'com.mcxiaoke.viewpagerindicator:library:1e8aad664137f68abdfee94889f6da3dc98be652a235176a403965a07a25de62',
]
}
project.ext.i2pbase = '../i2p.i2p'
project.ext.i2pbase = "../i2p.i2p"
def Properties props = new Properties()
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
@ -128,6 +119,24 @@ task copyI2PResources(type: Copy) {
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' } }

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

@ -16,6 +16,7 @@ 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;
@ -57,13 +58,13 @@ public class I2PActivityTest extends ActivityInstrumentationTestCase2<I2PActivit
public void testMainSwipe() {
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
onView(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), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
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), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
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

View File

@ -1,11 +1,16 @@
<?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" />
<application
android:icon="@drawable/ic_launcher_itoopie"
@ -51,6 +56,7 @@
<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" />

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(value);
}
public void onStartTrackingTouch(SeekBar seek) {
}
public void onStopTrackingTouch(SeekBar seek) {
}
public void setMax(int max) {
mMax = max;
}
public int getMax() {
return mMax;
}
public void setProgress(int progress) {
mValue = progress;
if (mSeekBar != null) {
mSeekBar.setProgress(progress);
}
}
public int getProgress() {
return mValue;
}
}

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import net.i2p.android.util.MemoryFragmentPagerAdapter;
import net.i2p.android.widget.CustomViewPager;
import android.support.v4.view.CustomViewPager;
import net.i2p.android.widget.SlidingTabLayout;
import net.i2p.router.RouterContext;
@ -147,10 +147,16 @@ public class I2PActivity extends I2PActivityBase implements
if (action == null)
return;
if (action.equals("net.i2p.android.router.START_I2P")) {
if (mViewPager.getCurrentItem() != 0)
mViewPager.setCurrentItem(0, false);
autoStart();
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;
}
}

View File

@ -5,14 +5,29 @@ import android.content.Context;
import net.i2p.android.router.NewsActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Notifications;
import net.i2p.crypto.SU3File;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.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.Log;
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
@ -50,18 +65,18 @@ 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(Context context, RouterContext ctx, Notifications notif) {
mCtx = context;
@ -73,7 +88,8 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
String last = ctx.getProperty(PROP_LAST_CHECKED);
if (last != null)
_lastFetch = Long.parseLong(last);
} catch (NumberFormatException nfe) {}
} catch (NumberFormatException nfe) {
}
File newsDir = new File(_context.getRouterDir(), NEWS_DIR);
// isn't already there on android
newsDir.mkdir();
@ -98,22 +114,24 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public String status() {
StringBuilder buf = new StringBuilder(128);
long now = _context.clock().now();
if (_lastUpdated > 0) {
buf.append(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();
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();
@ -139,7 +157,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
return true;
updateLastFetched();
String freq = _context.getProperty(PROP_REFRESH_FREQUENCY,
DEFAULT_REFRESH_FREQUENCY);
DEFAULT_REFRESH_FREQUENCY);
try {
long ms = Long.parseLong(freq);
if (ms <= 0)
@ -160,8 +178,9 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
/**
* Call this when changing news URLs to force an update next time the timer fires.
* @since 0.8.7
* Call this when changing news URLs to force an update next time the timer fires.
*
* @since 0.8.7
*/
void invalidateNews() {
_lastModified = null;
@ -169,7 +188,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
}
public void fetchNews() {
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL);
String newsURL = _context.getProperty(PROP_NEWS_URL, DEFAULT_NEWS_URL_SU3);
String proxyHost = "127.0.0.1";
int proxyPort = 4444;
if (_tempFile.exists())
@ -185,7 +204,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
} else {
// backup news location - always proxied
_tempFile.delete();
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL_SU3, true, null, _lastModified);
get.addStatusListener(this);
if (get.fetch())
_lastModified = get.getLastModified();
@ -195,22 +214,34 @@ 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(mCtx.getString(R.string.news_updated),
@ -229,13 +260,21 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_context.router().saveConfig();
}
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
// ignore
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (_log.shouldLog(Log.WARN))
_log.warn("Failed to fetch the news from " + url);
_tempFile.delete();
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
public void headerReceived(String url, int attemptNum, String key, String val) {
}
public void attempting(String url) {
}
private class Shutdown implements Runnable {
public void run() {
@ -244,4 +283,138 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
_thread.interrupt();
}
}
//
// SU3 handlers
//
/**
* Process the fetched su3 news file _tempFile.
* Handles 3 types of contained files: xml.gz (preferred), xml, and html (old format fake xml)
*
* @return the temp file contining the HTML-format news.xml
* @since 0.9.20
*/
private File processSU3() throws IOException {
SU3File su3 = new SU3File(_context, _tempFile);
// real xml, maybe gz, maybe not
File to1 = new File(_context.getTempDir(), "tmp-" + _context.random().nextInt() + ".xml");
// real xml
File to2 = new File(_context.getTempDir(), "tmp2-" + _context.random().nextInt() + ".xml");
try {
su3.verifyAndMigrate(to1);
int type = su3.getFileType();
if (su3.getContentType() != SU3File.CONTENT_NEWS)
throw new IOException("bad content type: " + su3.getContentType());
if (type == SU3File.TYPE_HTML)
return to1;
if (type != SU3File.TYPE_XML && type != SU3File.TYPE_XML_GZ)
throw new IOException("bad file type: " + type);
File xml;
if (type == SU3File.TYPE_XML_GZ) {
gunzip(to1, to2);
xml = to2;
to1.delete();
} else {
xml = to1;
}
NewsXMLParser parser = new NewsXMLParser(_context);
parser.parse(xml);
xml.delete();
NewsMetadata data = parser.getMetadata();
List<NewsEntry> entries = parser.getEntries();
String sudVersion = su3.getVersionString();
String signingKeyName = su3.getSignerString();
File to3 = new File(_context.getTempDir(), "tmp3-" + _context.random().nextInt() + ".xml");
outputOldNewsXML(data, entries, sudVersion, signingKeyName, to3);
return to3;
} finally {
to2.delete();
}
}
/**
* Gunzip the file
*
* @since 0.9.20
*/
private static void gunzip(File from, File to) throws IOException {
ReusableGZIPInputStream in = ReusableGZIPInputStream.acquire();
OutputStream out = null;
try {
in.initialize(new FileInputStream(from));
out = new SecureFileOutputStream(to);
byte buf[] = new byte[4096];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
ReusableGZIPInputStream.release(in);
}
}
/**
* Output in the old format.
*
* @since 0.9.20
*/
private void outputOldNewsXML(NewsMetadata data, List<NewsEntry> entries,
String sudVersion, String signingKeyName, File to) throws IOException {
NewsMetadata.Release latestRelease = data.releases.get(0);
Writer out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(to), "UTF-8"));
out.write("<!--\n");
// update metadata in old format
out.write("<i2p.release ");
if (latestRelease.i2pVersion != null)
out.write(" version=\"" + latestRelease.i2pVersion + '"');
if (latestRelease.minVersion != null)
out.write(" minVersion=\"" + latestRelease.minVersion + '"');
if (latestRelease.minJavaVersion != null)
out.write(" minJavaVersion=\"" + latestRelease.minJavaVersion + '"');
String su3Torrent = "";
String su2Torrent = "";
for (NewsMetadata.Update update : latestRelease.updates) {
if (update.torrent != null) {
if ("su3".equals(update.type))
su3Torrent = update.torrent;
else if ("su2".equals(update.type))
su2Torrent = update.torrent;
}
}
if (!su2Torrent.isEmpty())
out.write(" su2Torrent=\"" + su2Torrent + '"');
if (!su3Torrent.isEmpty())
out.write(" su3Torrent=\"" + su3Torrent + '"');
out.write("/>\n");
// su3 and feed metadata for debugging
out.write("** News version:\t" + DataHelper.stripHTML(sudVersion) + '\n');
out.write("** Signed by:\t" + signingKeyName + '\n');
out.write("** Feed:\t" + DataHelper.stripHTML(data.feedTitle) + '\n');
out.write("** Feed ID:\t" + DataHelper.stripHTML(data.feedID) + '\n');
out.write("** Feed Date:\t" + (new Date(data.feedUpdated)) + '\n');
out.write("-->\n");
if (entries == null)
return;
for (NewsEntry e : entries) {
if (e.title == null || e.content == null)
continue;
out.write("<!-- Entry Date: " + (new Date(e.updated)) + " -->\n");
out.write("<h3>");
out.write(e.title);
out.write("</h3>\n");
out.write(e.content);
out.write("\n\n");
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
}
}
}

View File

@ -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;
@ -84,7 +86,11 @@ 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);

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

@ -1,15 +1,19 @@
package net.i2p.android.i2ptunnel;
import android.annotation.TargetApi;
import android.app.Activity;
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;
@ -18,14 +22,17 @@ 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;
@ -37,6 +44,7 @@ public class TunnelDetailFragment extends Fragment {
private TunnelControllerGroup mGroup;
private TunnelEntry mTunnel;
private Toolbar mToolbar;
private ImageView mStatus;
public static TunnelDetailFragment newInstance(int tunnelId) {
TunnelDetailFragment f = new TunnelDetailFragment();
@ -69,21 +77,33 @@ public class TunnelDetailFragment extends Fragment {
super.onCreate(savedInstanceState);
String error;
List<TunnelController> controllers;
try {
mGroup = TunnelControllerGroup.getInstance();
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
controllers = 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();
}
}
}
@ -103,6 +123,10 @@ public class TunnelDetailFragment extends Fragment {
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());
@ -112,8 +136,26 @@ 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();
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);
@ -178,11 +220,17 @@ public class TunnelDetailFragment extends Fragment {
Uri uri = mTunnel.getRecommendedAppForTunnel();
if (uri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(getContext(),
R.string.no_market_app,
Toast.LENGTH_LONG).show();
}
}
}
})
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
@ -218,6 +266,14 @@ public class TunnelDetailFragment extends Fragment {
}
}
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;
@ -231,6 +287,8 @@ public class TunnelDetailFragment extends Fragment {
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// 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();
@ -239,39 +297,84 @@ public class TunnelDetailFragment extends Fragment {
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
// 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() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.i2ptunnel_delete_confirm_message)
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
List<String> msgs = TunnelUtil.deleteTunnel(
I2PAppContext.getGlobalContext(),
mGroup, mTunnel.getId(), null);
dialog.dismiss();
Toast.makeText(getActivity().getApplicationContext(),
msgs.get(0), Toast.LENGTH_LONG).show();
mCallback.onTunnelDeleted(mTunnel.getId(),
mGroup.getControllers().size());
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
};
dg.show(getFragmentManager(), "delete_tunnel_dialog");
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

@ -6,15 +6,19 @@ import android.net.Uri;
import android.widget.Toast;
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;
public class TunnelEntry {
public static final int RUNNING = 1;
@ -26,18 +30,32 @@ public class TunnelEntry {
private final TunnelController mController;
private final int mId;
/**
* @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(
I2PAppContext.getGlobalContext(), tcg, -1, cfg);
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) {
@ -252,7 +270,7 @@ public class TunnelEntry {
if (isClient())
details = getClientDestination();
else
details = "";
details = getDestHashBase32();
return details;
}
@ -262,6 +280,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

@ -3,6 +3,7 @@ package net.i2p.android.i2ptunnel;
import android.content.Context;
import android.os.Build;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@ -13,13 +14,14 @@ import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.util.FragmentUtils;
import java.util.ArrayList;
import java.util.List;
public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mCtx;
private boolean mClientTunnels;
private TunnelListFragment.OnTunnelSelectedListener mListener;
private FragmentUtils.TwoPaneProvider mTwoPane;
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.
@ -33,10 +35,10 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
}
public static class TunnelViewHolder extends RecyclerView.ViewHolder {
public ImageView status;
public TextView name;
public TextView description;
public TextView interfacePort;
public final ImageView status;
public final TextView name;
public final TextView description;
public final TextView interfacePort;
public TunnelViewHolder(View itemView) {
super(itemView);
@ -64,8 +66,15 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
}
public void addTunnel(TunnelEntry tunnel) {
if (mTunnels == null)
mTunnels = new ArrayList<TunnelEntry>();
boolean wasEmpty = mTunnels.isEmpty();
mTunnels.add(tunnel);
notifyItemInserted(mTunnels.size()-1);
if (wasEmpty) {
notifyDataSetChanged();
} else {
notifyItemInserted(mTunnels.size() - 1);
}
}
public TunnelEntry getTunnel(int position) {
@ -138,6 +147,8 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
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());
@ -151,9 +162,11 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
mActivatedPosition = position;
notifyItemChanged(oldPosition);
notifyItemChanged(position);
mListener.onTunnelSelected(tunnel.getId(),
Pair.create((View)tvh.name, mCtx.getString(R.string.TUNNEL_NAME)),
Pair.create((View)tvh.description, mCtx.getString(R.string.TUNNEL_DESCRIPTION)));
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);
}
});
break;

View File

@ -13,10 +13,10 @@ 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) {

View File

@ -53,8 +53,7 @@ public class TunnelListFragment extends Fragment implements
// Container Activity must implement this interface
public interface OnTunnelSelectedListener {
void onTunnelSelected(int tunnelId, Pair<View, String> tunnelName,
Pair<View, String> tunnelDescription);
void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs);
}
public static TunnelListFragment newInstance(boolean showClientTunnels) {
@ -151,14 +150,21 @@ public class TunnelListFragment extends Fragment implements
};
public void updateState(State state) {
if (state == State.STOPPING || state == State.STOPPED ||
state == State.MANUAL_STOPPING ||
state == State.MANUAL_STOPPED ||
state == State.MANUAL_QUITTING ||
state == State.MANUAL_QUITTED)
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
else
initTunnels();
try {
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() {

View File

@ -2,6 +2,7 @@ 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;
@ -21,26 +22,51 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(R.string.i2ptunnel_wizard_submit_confirm_message)
.setPositiveButton(R.string.i2ptunnel_wizard_submit_confirm_button,
new DialogInterface.OnClickListener() {
return FinishWizardDialogFragment.newInstance();
}
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
result.putExtra(TunnelsContainer.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

@ -65,6 +65,14 @@ public class TunnelsContainer extends Fragment implements
setHasOptionsMenu(true);
}
private boolean showActions() {
RouterContext rCtx = Util.getRouterContext();
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
return rCtx != null && tcg != null &&
(tcg.getState() == ClientAppState.STARTING ||
tcg.getState() == ClientAppState.RUNNING);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.container_tunnels, container, false);
@ -72,6 +80,7 @@ public class TunnelsContainer extends Fragment implements
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
@ -154,17 +163,16 @@ public class TunnelsContainer extends Fragment implements
@Override
public void onPrepareOptionsMenu(Menu menu) {
RouterContext rCtx = Util.getRouterContext();
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
boolean showActions = rCtx != null && tcg != null &&
(tcg.getState() == ClientAppState.STARTING ||
tcg.getState() == ClientAppState.RUNNING);
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);
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
// Was causing a NPE in version 4745238 (0.9.31)
if (mNewTunnel != null) {
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
}
}
@Override
@ -183,7 +191,10 @@ public class TunnelsContainer extends Fragment implements
msgs = tcg.stopAllControllers();
break;
case R.id.action_restart_all_tunnels:
msgs = tcg.restartAllControllers();
// 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);
@ -200,15 +211,20 @@ public class TunnelsContainer extends Fragment implements
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();
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
if (tunnel.isClient() && mClientFrag != null)
mClientFrag.addTunnel(tunnel);
else if (mServerFrag != null)
mServerFrag.addTunnel(tunnel);
if (tunnel != null) {
if (tunnel.isClient() && mClientFrag != null)
mClientFrag.addTunnel(tunnel);
else if (mServerFrag != null)
mServerFrag.addTunnel(tunnel);
}
}
}
}
@ -239,8 +255,7 @@ public class TunnelsContainer extends Fragment implements
// TunnelListFragment.OnTunnelSelectedListener
public final void onTunnelSelected(int tunnelId, Pair<View, String> tunnelName,
Pair<View, String> tunnelDescription) {
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
@ -255,7 +270,7 @@ public class TunnelsContainer extends Fragment implements
detailIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
getActivity(), tunnelName, tunnelDescription);
getActivity(), pairs);
ActivityCompat.startActivity(getActivity(), detailIntent, options.toBundle());
}
}

View File

@ -3,11 +3,11 @@ package net.i2p.android.i2ptunnel.preferences;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.support.v7.app.AlertDialog;
import 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;

View File

@ -2,27 +2,29 @@ package net.i2p.android.i2ptunnel.preferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.support.v4.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.widget.Toast;
import net.i2p.I2PAppContext;
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;
public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
import java.util.concurrent.ExecutionException;
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
protected static final String ARG_TUNNEL_ID = "tunnelId";
protected TunnelControllerGroup mGroup;
protected int mTunnelId;
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
String error;
try {
mGroup = TunnelControllerGroup.getInstance();
@ -33,13 +35,30 @@ public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
}
if (mGroup == null) {
// TODO Show error
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);
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
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));
loadPreferences();
try {
loadPreferences();
} catch (IllegalArgumentException iae) {
// mGroup couldn't load its config file
Toast.makeText(getActivity().getApplicationContext(),
iae.toString(), Toast.LENGTH_LONG).show();
getActivity().finish();
}
}
}
@ -64,7 +83,17 @@ public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
private void saveTunnel() {
if (mGroup != null) {
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
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();
} catch (InterruptedException e) {
Util.e("Interrupted while saving tunnel config", e);
} catch (ExecutionException e) {
Util.e("Error while saving tunnel config", e);
}
}
}

View File

@ -1,16 +1,17 @@
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.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
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;
@ -110,7 +111,8 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_SHARED_CLIENT)));
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_PORT)));
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
// # TODO: See trac issue #2296
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
}
@ -134,10 +136,18 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
@Override
protected Void doInBackground(Void... voids) {
Set<String> interfaceSet = Addresses.getAllAddresses();
String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
reachableBy.setEntries(interfaces);
reachableBy.setEntryValues(interfaces);
reachableBy.setEnabled(true);
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();
@ -159,8 +169,11 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
@Override
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
/*
# TODO: See trac issue #2296
if (!isStandardOrIrc)
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
*/
}
@Override
@ -176,7 +189,8 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
@Override
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
// # 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)));
@ -192,7 +206,8 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
protected void generalServerPortStreamr(boolean isStreamr) {
if (isStreamr) {
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_HOST)));
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
// # TODO: See trac issue #2296
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
}
}

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

@ -11,7 +11,7 @@ package net.i2p.android.i2ptunnel.util;
* tunnel properties always exist.
*/
public abstract class TunnelLogic {
protected String mType;
protected final String mType;
public TunnelLogic(String type) {
mType = type;

View File

@ -99,6 +99,7 @@ public class TunnelUtil extends GeneralHelper {
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();
@ -122,9 +123,9 @@ public class TunnelUtil extends GeneralHelper {
}
class TunnelToPreferences extends TunnelLogic {
SharedPreferences.Editor ed;
Resources res;
int tunnel;
final SharedPreferences.Editor ed;
final Resources res;
final int tunnel;
public TunnelToPreferences(SharedPreferences.Editor ed, Resources res, int tunnel, String type) {
super(type);
@ -182,8 +183,10 @@ public class TunnelUtil extends GeneralHelper {
@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
@ -212,7 +215,8 @@ public class TunnelUtil extends GeneralHelper {
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
ed.putString(res.getString(R.string.TUNNEL_TARGET_HOST), getTargetHost(tunnel));
ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
// # TODO: See trac issue #2296
//ed.putBoolean(res.getString(R.string.TUNNEL_USE_SSL), isSSLEnabled(tunnel));
}
}
@ -225,7 +229,7 @@ public class TunnelUtil extends GeneralHelper {
ed.putInt(res.getString(R.string.TUNNEL_OPT_QUANTITY),
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_QUANTITY)));
ed.putInt(res.getString(R.string.TUNNEL_OPT_BACKUP_QUANTITY),
getTunnelQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
getTunnelBackupQuantity(tunnel, res.getInteger(R.integer.DEFAULT_TUNNEL_BACKUP_QUANTITY)));
}
@Override
@ -325,6 +329,7 @@ public class TunnelUtil extends GeneralHelper {
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();
@ -345,11 +350,11 @@ public class TunnelUtil extends GeneralHelper {
}
class TunnelConfigFromPreferences extends TunnelLogic {
TunnelConfig cfg;
SharedPreferences prefs;
Resources res;
TunnelControllerGroup tcg;
int tunnel;
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) {
@ -414,8 +419,10 @@ public class TunnelUtil extends GeneralHelper {
@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
@ -444,7 +451,8 @@ public class TunnelUtil extends GeneralHelper {
protected void generalServerPortStreamr(boolean isStreamr) {
if (!isStreamr) {
cfg.setTargetHost(prefs.getString(res.getString(R.string.TUNNEL_TARGET_HOST), "127.0.0.1"));
cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
// # TODO: See trac issue #2296
//cfg.setUseSSL(prefs.getBoolean(res.getString(R.string.TUNNEL_USE_SSL), false));
}
}
@ -560,10 +568,17 @@ public class TunnelUtil extends GeneralHelper {
}
}
/**
* @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();
@ -585,10 +600,10 @@ public class TunnelUtil extends GeneralHelper {
}
class TunnelConfigFromWizard extends TunnelLogic {
TunnelConfig cfg;
Bundle data;
Resources res;
TunnelControllerGroup tcg;
final TunnelConfig cfg;
final Bundle data;
final Resources res;
final TunnelControllerGroup tcg;
public TunnelConfigFromWizard(TunnelConfig cfg, Bundle data, Resources res,
TunnelControllerGroup tcg, String type) {

View File

@ -1,20 +1,18 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import android.preference.Preference;
import android.support.v4.app.Fragment;
import android.support.v4.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
public class AdvancedPreferenceFragment extends PreferenceFragment {
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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_advanced);
findPreference(PREFERENCE_CATEGORY_TRANSPORTS)

View File

@ -7,8 +7,7 @@ import net.i2p.android.router.SettingsActivity;
public class AppearancePreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_appearance);
}

View File

@ -7,8 +7,7 @@ import net.i2p.android.router.SettingsActivity;
public class ExploratoryPoolPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_expl_tunnels);
}

View File

@ -2,10 +2,10 @@ package net.i2p.android.preferences;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import 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;
@ -24,8 +24,7 @@ public class GraphsPreferenceFragment extends I2PreferenceFragment {
public static final String GRAPH_PREFERENCES_SEEN = "graphPreferencesSeen";
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings();
}

View File

@ -1,9 +1,9 @@
package net.i2p.android.preferences;
import android.support.v4.preference.PreferenceFragment;
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;
@ -15,7 +15,7 @@ import java.util.Set;
/**
* A PreferenceFragment that handles saving router settings.
*/
public class I2PreferenceFragment extends PreferenceFragment {
public abstract class I2PreferenceFragment extends CustomPreferenceFragment {
@Override
public void onPause() {
List<Properties> lProps = Util.getPropertiesFromPreferences(getActivity());

View File

@ -1,7 +1,7 @@
package net.i2p.android.preferences;
import android.os.Bundle;
import android.preference.PreferenceScreen;
import android.support.v7.preference.PreferenceScreen;
import net.i2p.android.router.R;
import net.i2p.android.router.SettingsActivity;
@ -11,8 +11,7 @@ import net.i2p.util.LogManager;
public class LoggingPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings();
}

View File

@ -7,8 +7,7 @@ import net.i2p.android.router.SettingsActivity;
public class NetworkPreferenceFragment extends I2PreferenceFragment {
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
addPreferencesFromResource(R.xml.settings_net);
}

View File

@ -3,22 +3,21 @@ package net.i2p.android.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.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.router.util.PortPreference;
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 onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
// Load any properties that the router might have changed on us.
loadProperties();
addPreferencesFromResource(R.xml.settings_transports);

View File

@ -1,9 +1,8 @@
package net.i2p.android.router.util;
package net.i2p.android.preferences.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.EditTextPreference;
import android.text.InputType;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
import net.i2p.android.router.R;
@ -26,7 +25,6 @@ public class ConnectionLimitPreference extends EditTextPreference {
}
void init(Context context, AttributeSet attrs) {
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ConnectionLimitPreference, 0, 0);
mValueInTitle = attr.getBoolean(R.styleable.ConnectionLimitPreference_clp_valueInTitle, false);
attr.recycle();
@ -70,6 +68,10 @@ public class ConnectionLimitPreference extends EditTextPreference {
@Override
protected boolean persistString(String value) {
return value != null && 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 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,25 +1,21 @@
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
@ -42,6 +38,10 @@ public class IntEditTextPreference extends EditTextPreference {
@Override
protected boolean persistString(String value) {
return value != null && 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

@ -1,7 +1,7 @@
package net.i2p.android.router.util;
package net.i2p.android.preferences.util;
import android.content.Context;
import android.preference.ListPreference;
import android.support.v7.preference.ListPreference;
import android.util.AttributeSet;
public class IntListPreference extends ListPreference {

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

@ -1,7 +1,7 @@
package net.i2p.android.router.util;
package net.i2p.android.preferences.util;
import android.content.Context;
import android.preference.EditTextPreference;
import android.support.v7.preference.EditTextPreference;
import android.util.AttributeSet;
public class SummaryEditTextPreference extends EditTextPreference {

View File

@ -63,13 +63,13 @@ public class ConsoleContainer extends Fragment {
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_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) {
@ -87,20 +87,19 @@ public class ConsoleContainer extends Fragment {
inflater.inflate(R.menu.activity_main_actions, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
setMenuVisibility();
}
private void setMenuVisibility() {
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(
"i2pandroid.main.showStats", false);
boolean routerRunning = Util.getRouterContext() != null;
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_graphs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_peers).setVisibility(advanced && routerRunning ? View.VISIBLE : View.GONE);
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(advanced && routerRunning ? View.VISIBLE : View.GONE);
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

View File

@ -328,7 +328,7 @@ public class MainFragment extends I2PFragmentBase {
newState == State.NETWORK_STOPPED) {
mConsoleLights.setImageResource(R.drawable.routerlogo_0);
} else if (newState == State.STARTING ||
newState == State.GRACEFUL_SHUTDOWN ||
//newState == State.GRACEFUL_SHUTDOWN || // Don't change lights for graceful
newState == State.STOPPING ||
newState == State.MANUAL_STOPPING ||
newState == State.MANUAL_QUITTING ||
@ -352,7 +352,10 @@ public class MainFragment extends I2PFragmentBase {
vNetStatusText.setText(R.string.no_internet);
vStatusContainer.setVisibility(View.VISIBLE);
vNonNetStatus.setVisibility(View.GONE);
} else if (ctx != null) {
} else if (lastRouterState != null &&
!Util.isStopping(lastRouterState) &&
!Util.isStopped(lastRouterState) &&
ctx != null) {
Util.NetStatus netStatus = Util.getNetStatus(getActivity(), ctx);
switch (netStatus.level) {
case ERROR:
@ -425,9 +428,9 @@ public class MainFragment extends I2PFragmentBase {
double outData = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes();
((TextView) getActivity().findViewById(R.id.console_download_stats)).setText(
Util.formatSize(inBw) + "Bps / " + Util.formatSize(inData) + "B");
Util.formatSpeed(inBw) + "Bps / " + Util.formatSize(inData) + "B");
((TextView) getActivity().findViewById(R.id.console_upload_stats)).setText(
Util.formatSize(outBw) + "Bps / " + Util.formatSize(outData) + "B");
Util.formatSpeed(outBw) + "Bps / " + Util.formatSize(outData) + "B");
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.VISIBLE);
} else {
@ -528,7 +531,7 @@ public class MainFragment extends I2PFragmentBase {
public AlphaComparator(RouterContext ctx) {
_ctx = ctx;
xsc = _(ctx, SHARED_CLIENTS);
xsc = _t(ctx, SHARED_CLIENTS);
}
public int compare(Destination lhs, Destination rhs) {
@ -556,12 +559,12 @@ public class MainFragment extends I2PFragmentBase {
if (name == null)
name = d.calculateHash().toBase64().substring(0, 6);
else
name = _(ctx, name);
name = _t(ctx, name);
return name;
}
private String _(RouterContext ctx, String s) {
private String _t(RouterContext ctx, String s) {
if (SHARED_CLIENTS.equals(s))
return getString(R.string.shared_clients);
else
@ -645,9 +648,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

@ -3,12 +3,12 @@ package net.i2p.android.router;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.preference.PreferenceFragment;
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 net.i2p.android.I2PActivity;
@ -84,10 +84,11 @@ public class SettingsActivity extends AppCompatActivity implements
}
}
public static class SettingsFragment extends PreferenceFragment {
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
public void onCreatePreferences(Bundle paramBundle, String s) {
migrateOldSettings();
addPreferencesFromResource(R.xml.settings);
this.findPreference(PREFERENCE_CATEGORY_NETWORK)
@ -104,6 +105,29 @@ public class SettingsActivity extends AppCompatActivity implements
.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();

View File

@ -2,6 +2,7 @@ package net.i2p.android.router.addressbook;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
@ -20,26 +21,51 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage("Add to private addressbook?")
.setPositiveButton("Add",
new DialogInterface.OnClickListener() {
return FinishWizardDialogFragment.newInstance();
}
public void onClick(DialogInterface dialog, int which) {
Intent result = new Intent();
setResult(Activity.RESULT_OK, result);
result.putExtra(AddressbookContainer.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

@ -144,11 +144,14 @@ public class AddressbookContainer extends Fragment
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.container_addressbook_actions, menu);
SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));
searchView.setOnQueryTextListener(this);
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

View File

@ -26,7 +26,6 @@ import com.eowise.recyclerview.stickyheaders.StickyHeadersBuilder;
import com.eowise.recyclerview.stickyheaders.StickyHeadersItemDecoration;
import com.pnikosis.materialishprogress.ProgressWheel;
import net.i2p.addressbook.Daemon;
import net.i2p.android.router.R;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
@ -167,15 +166,23 @@ public class AddressbookFragment extends Fragment implements
int loaderId = PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID;
if (state == State.STOPPING || state == State.STOPPED ||
state == State.MANUAL_STOPPING ||
state == State.MANUAL_STOPPED ||
state == State.MANUAL_QUITTING ||
state == State.MANUAL_QUITTED)
getLoaderManager().destroyLoader(loaderId);
else {
mRecyclerView.setLoading(true);
getLoaderManager().initLoader(loaderId, null, this);
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
}
}
@ -207,8 +214,11 @@ public class AddressbookFragment extends Fragment implements
mAddToAddressbook.setVisibility(rCtx == null ? View.GONE : View.VISIBLE);
// Only show "Reload subscriptions" for router addressbook
menu.findItem(R.id.action_reload_subscriptions).setVisible(
rCtx != null && !PRIVATE_BOOK.equals(mBook));
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
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
@ -223,9 +233,12 @@ public class AddressbookFragment extends Fragment implements
switch (item.getItemId()) {
case R.id.action_reload_subscriptions:
Daemon.wakeup();
Toast.makeText(getActivity(), "Reloading subscriptions...",
Toast.LENGTH_SHORT).show();
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);

View File

@ -61,7 +61,7 @@ public class AddressbookSettingsActivity extends AppCompatActivity {
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;
}

View File

@ -2,6 +2,10 @@ package net.i2p.android.router.dialog;
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;
@ -14,6 +18,8 @@ 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
@ -23,8 +29,20 @@ 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)

View File

@ -60,7 +60,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
}
ret.add(versions);
ret.add(countries);
//ret.add(countries);
ret.add(transports);
return ret;
@ -95,7 +95,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
int rv = 0;
for (RouterAddress addr : info.getAddresses()) {
String style = addr.getTransportStyle();
if (style.equals("NTCP")) {
if (style.equals("NTCP") || style.equals("NTCP2")) {
rv |= NTCP;
} else if (style.equals("SSU")) {
if (addr.getOption("iport0") != null)
@ -108,7 +108,12 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
rv |= IPV6;
}
return getContext().getString(TNAMES[rv]);
int tname = TNAMES[rv];
// remap cases with no string to "Hidden or starting up"
// so we don't crash NotFoundException
if (tname == 0)
tname = TNAMES[0];
return getContext().getString(tname);
}
@Override

View File

@ -113,15 +113,15 @@ public class NetDbSummaryPagerFragment extends I2PFragmentBase implements
if (mData == null)
return 0;
else
return 3;
return 2;
}
@Override
public CharSequence getPageTitle(int i) {
switch (i) {
case 1:
return getString(R.string.countries);
case 2:
// return getString(R.string.countries);
//case 2:
return getString(R.string.settings_label_transports);
default:
return getString(R.string.versions);

View File

@ -51,7 +51,7 @@ public class NetDbSummaryTableFragment extends Fragment {
switch (mCategory) {
case 1:
case 2:
//case 2:
Collections.sort(objects);
break;
default:
@ -83,9 +83,9 @@ public class NetDbSummaryTableFragment extends Fragment {
switch (mCategory) {
case 1:
tl1.setText(R.string.country);
break;
case 2:
// tl1.setText(R.string.country);
// break;
//case 2:
tl1.setText(R.string.transport);
break;
default:

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import net.i2p.android.router.util.Util;
import net.i2p.android.router.I2PConstants;
import net.i2p.android.router.service.RouterService;
@ -19,7 +20,12 @@ public class OnBootReceiver extends BroadcastReceiver implements I2PConstants {
if (startOnBoot) {
Intent routerService = new Intent(context, RouterService.class);
context.startService(routerService);
// Ticket #2404
try {
context.startService(routerService);
} catch (IllegalStateException ex) {
Util.e("Error: ", ex);
}
}
}
}

View File

@ -23,10 +23,8 @@ import net.i2p.data.DataHelper;
import net.i2p.router.Job;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterLaunch;
import java.lang.ref.WeakReference;
import java.text.DecimalFormat;
/**
* Runs the router
@ -214,16 +212,18 @@ public class RouterService extends Service {
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
// Launch the router!
RouterLaunch.main(null);
// TODO Store this somewhere instead of relying on global context?
Router r = new Router();
r.runRouter();
synchronized(_stateLock) {
if(_state != State.STARTING) {
return;
}
setState(State.RUNNING);
_statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_running);
_context = Util.getRouterContext();
_context = r.getContext();
if (_context == null) {
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
throw new IllegalStateException("Router has no context?");
}
_context.router().setKillVMOnEnd(false);
Job loadJob = new LoadClientsJob(RouterService.this, _context, _notif);
@ -260,26 +260,16 @@ public class RouterService extends Service {
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
// control total width
DecimalFormat fmt;
if(inBW >= 1000 || outBW >= 1000) {
fmt = new DecimalFormat("#0");
} else if(inBW >= 100 || outBW >= 100) {
fmt = new DecimalFormat("#0.0");
} else {
fmt = new DecimalFormat("#0.00");
}
double inBW = ctx.bandwidthLimiter().getReceiveBps();
double outBW = ctx.bandwidthLimiter().getSendBps();
String text =
getResources().getString(R.string.notification_status_text,
Util.formatSize(inBW), Util.formatSize(outBW));
Util.formatSpeed(inBW), Util.formatSpeed(outBW));
// TODO change string resource after 0.9.20 to use Util.formatSize()
String bigText =
getResources().getString(R.string.notification_status_bw,
fmt.format(inBW), fmt.format(outBW)) + '\n'
Util.formatSpeed(inBW), Util.formatSpeed(outBW)) + '\n'
+ getResources().getString(R.string.notification_status_peers,
active, known) + '\n'
+ getResources().getString(R.string.notification_status_expl,

View File

@ -40,6 +40,7 @@ class StatusBar {
mNotifyBuilder = new NotificationCompat.Builder(ctx)
.setContentText(text)
.setSmallIcon(icon)
.setColor(mCtx.getResources().getColor(R.color.primary_light))
.setOngoing(true)
.setOnlyAlertOnce(true);

View File

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

View File

@ -39,7 +39,7 @@ public class PeersFragment extends I2PFragmentBase {
wv.getSettings().setLoadsImagesAutomatically(true); // was false
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
wv.getSettings().setUseWideViewPort(true);
_wvClient = new I2PWebViewClient();
_wvClient = new I2PWebViewClient(this);
wv.setWebViewClient(_wvClient);
wv.getSettings().setBuiltInZoomControls(true);
return v;

View File

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

View File

@ -32,7 +32,17 @@ public class NamingServiceUtil {
String kDest = res.getString(R.string.addressbook_add_wizard_k_destination);
String hostName = data.getBundle(kHostName).getString(Page.SIMPLE_DATA_KEY);
String host = toASCII(res, hostName); // Already validated, won't throw IAE
String host;
try {
// Already validated, won't throw IAE
// ^^^ NOT TRUE ticket #2489 ^^^
host = toASCII(res, hostName);
} catch (IllegalArgumentException iae) {
Toast.makeText(ctx,
iae.getMessage(),
Toast.LENGTH_LONG).show();
return false;
}
String displayHost = host.equals(hostName) ? hostName :
hostName + " (" + host + ')';

View File

@ -1,6 +1,7 @@
package net.i2p.android.router.util;
import net.i2p.android.router.R;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@ -18,7 +19,7 @@ public class Notifications {
mNotificationManager = (NotificationManager) ctx.getSystemService(
Context.NOTIFICATION_SERVICE);
}
public void notify(String title, String text) {
notify(title, text, null);
}
@ -26,10 +27,11 @@ public class Notifications {
public void notify(String title, String text, Class<?> c) {
NotificationCompat.Builder b =
new NotificationCompat.Builder(mCtx)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(ICON)
.setAutoCancel(true);
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(ICON)
.setColor(mCtx.getResources().getColor(R.color.primary_light))
.setAutoCancel(true);
if (c != null) {
Intent intent = new Intent(mCtx, c);

View File

@ -49,7 +49,8 @@ public abstract class Util implements I2PConstants {
//System.err.println("APK Path" + ": " + _apkPath);
if (pi.versionName != null)
return pi.versionName;
} catch (Exception e) {}
} catch (Exception e) {
}
return "??";
}
@ -60,7 +61,7 @@ public abstract class Util implements I2PConstants {
*/
public static RouterContext getRouterContext() {
List<RouterContext> contexts = RouterContext.listContexts();
if ( !((contexts == null) || (contexts.isEmpty())) ) {
if (!((contexts == null) || (contexts.isEmpty()))) {
return contexts.get(0);
}
return null;
@ -73,8 +74,8 @@ public abstract class Util implements I2PConstants {
}
/**
* Log to the context logger if available (which goes to the console buffer
* and to logcat), else just to logcat.
* Log to the context logger if available (which goes to the console buffer
* and to logcat), else just to logcat.
*/
public static void e(String m, Throwable t) {
I2PAppContext ctx = I2PAppContext.getCurrentContext();
@ -119,6 +120,7 @@ public abstract class Util implements I2PConstants {
android.util.Log.i(ANDROID_TAG, m);
}
}
public static void d(String m) {
d(m, null);
}
@ -135,7 +137,9 @@ public abstract class Util implements I2PConstants {
}
}
/** copied from various private components */
/**
* copied from various private components
*/
final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
@ -229,6 +233,7 @@ public abstract class Util implements I2PConstants {
// propName -> defaultValue
private static HashMap<String, Boolean> booleanOptionsRequiringRestart = new HashMap<>();
private static HashMap<String, String> stringOptionsRequiringRestart = new HashMap<>();
static {
HashMap<String, Boolean> boolToAdd = new HashMap<>();
HashMap<String, String> strToAdd = new HashMap<>();
@ -245,6 +250,7 @@ public abstract class Util implements I2PConstants {
booleanOptionsRequiringRestart.putAll(boolToAdd);
stringOptionsRequiringRestart.putAll(strToAdd);
}
/**
* This function performs two tasks:
* <ul><li>
@ -256,7 +262,7 @@ public abstract class Util implements I2PConstants {
* changed that will require a router restart.
* </li></ul>
*
* @param props a Properties object containing the router.config
* @param props a Properties object containing the router.config
* @param toRemove a Collection of properties that will be removed
* @return true if the router needs to be restarted.
*/
@ -293,30 +299,38 @@ public abstract class Util implements I2PConstants {
public static String getFileDir(Context context) {
// This needs to be changed so that we can have an alternative place
return context.getFilesDir().getAbsolutePath();
File f = context.getFilesDir();
if (f == null) {
// https://code.google.com/p/android/issues/detail?id=8886
// Seems to be a race condition; try again.
// Supposedly only in pre-4.4 devices, but this was observed on a
// Samsung Galaxy Grand Prime (grandprimeve3g), 1024MB RAM, Android 5.1
f = context.getFilesDir();
}
return f.getAbsolutePath();
}
/**
* Write properties to a file. If the file does not exist, it is created.
* If the properties already exist in the file, they are updated.
* Write properties to a file. If the file does not exist, it is created.
* If the properties already exist in the file, they are updated.
*
* @param dir the file directory
* @param file relative to dir
* @param props properties to set
* @param dir the file directory
* @param file relative to dir
* @param props properties to set
*/
public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) {
mergeResourceToFile(ctx, dir, file, 0, props, null);
}
/**
* Load defaults from resource, then add props from settings, and write back.
* If resID is 0, defaults are not written over the existing file content.
* Load defaults from resource, then add props from settings, and write back.
* If resID is 0, defaults are not written over the existing file content.
*
* @param dir the file directory
* @param file relative to dir
* @param resID the ID of the default resource, or 0
* @param userProps local properties or null
* @param toRemove properties to remove, or null
* @param dir the file directory
* @param file relative to dir
* @param resID the ID of the default resource, or 0
* @param userProps local properties or null
* @param toRemove properties to remove, or null
*/
public static void mergeResourceToFile(Context ctx, String dir, String file, int resID,
Properties userProps, Collection<String> toRemove) {
@ -343,7 +357,7 @@ public abstract class Util implements I2PConstants {
if (resID > 0)
in = ctx.getResources().openRawResource(resID);
if (in != null)
DataHelper.loadProps(props, in);
DataHelper.loadProps(props, in);
// override with user settings
if (userProps != null)
@ -356,12 +370,18 @@ public abstract class Util implements I2PConstants {
File path = new File(dir, file);
DataHelper.storeProps(props, path);
Util.d("Saved " + props.size() +" properties in " + file);
Util.d("Saved " + props.size() + " properties in " + file);
} catch (IOException ioe) {
} catch (Resources.NotFoundException nfe) {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
if (in != null) try {
in.close();
} catch (IOException ioe) {
}
if (fin != null) try {
fin.close();
} catch (IOException ioe) {
}
}
}
@ -393,10 +413,11 @@ public abstract class Util implements I2PConstants {
this.status = status;
}
}
public static NetStatus getNetStatus(Context ctx, RouterContext rCtx) {
if (rCtx.commSystem().isDummy())
return new NetStatus(NetStatus.Level.INFO, ctx.getString(R.string.vm_comm_system));
if (rCtx.router().getUptime() > 60*1000 && (!rCtx.router().gracefulShutdownInProgress()) &&
if (rCtx.router().getUptime() > 60 * 1000 && (!rCtx.router().gracefulShutdownInProgress()) &&
!rCtx.clientManager().isAlive()) // not a router problem but the user should know
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_i2cp));
// Warn based on actual skew from peers, not update status, so if we successfully offset
@ -404,9 +425,12 @@ public abstract class Util implements I2PConstants {
//if (!rCtx.clock().getUpdatedSuccessfully())
long skew = rCtx.commSystem().getFramedAveragePeerClockSkew(33);
// Display the actual skew, not the offset
if (Math.abs(skew) > 30*1000)
if (Math.abs(skew) > 30 * 1000)
return new NetStatus(NetStatus.Level.ERROR,
ctx.getString(R.string.net_status_error_skew, DataHelper.formatDuration2(Math.abs(skew))));
ctx.getString(R.string.net_status_error_skew,
DataHelper.formatDuration2(Math.abs(skew))
.replace("&minus;", "-")
.replace("&nbsp;", " ")));
if (rCtx.router().isHidden())
return new NetStatus(NetStatus.Level.INFO, ctx.getString(R.string.hidden));
RouterInfo routerInfo = rCtx.router().getRouterInfo();
@ -421,10 +445,15 @@ public abstract class Util implements I2PConstants {
case IPV4_UNKNOWN_IPV6_OK:
case IPV4_DISABLED_IPV6_OK:
case IPV4_SNAT_IPV6_OK:
RouterAddress ra = routerInfo.getTargetAddress("NTCP");
if (ra == null)
List<RouterAddress> ras = routerInfo.getTargetAddresses("NTCP", "NTCP2");
if (ras.isEmpty())
return new NetStatus(NetStatus.Level.INFO, toStatusString(ctx, status));
byte[] ip = ra.getIP();
byte[] ip = null;
for (RouterAddress ra : ras) {
ip = ra.getIP();
if (ip != null)
break;
}
if (ip == null)
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_unresolved_tcp));
// TODO set IPv6 arg based on configuration?
@ -438,7 +467,7 @@ public abstract class Util implements I2PConstants {
case REJECT_UNSOLICITED:
case IPV4_DISABLED_IPV6_FIREWALLED:
if (routerInfo.getTargetAddress("NTCP") != null)
if (routerInfo.getTargetAddresses("NTCP", "NTCP2").isEmpty())
return new NetStatus(NetStatus.Level.WARN, ctx.getString(R.string.net_status_warn_firewalled_inbound_tcp));
// fall through...
case IPV4_FIREWALLED_IPV6_OK:
@ -459,8 +488,8 @@ public abstract class Util implements I2PConstants {
case IPV4_UNKNOWN_IPV6_FIREWALLED:
case IPV4_DISABLED_IPV6_UNKNOWN:
default:
ra = routerInfo.getTargetAddress("SSU");
if (ra == null && rCtx.router().getUptime() > 5*60*1000) {
RouterAddress ra = routerInfo.getTargetAddress("SSU");
if (ra == null && rCtx.router().getUptime() > 5 * 60 * 1000) {
if (rCtx.commSystem().countActivePeers() <= 0)
return new NetStatus(NetStatus.Level.ERROR, ctx.getString(R.string.net_status_error_no_active_peers));
else if (rCtx.getProperty(ctx.getString(R.string.PROP_I2NP_NTCP_HOSTNAME)) == null ||
@ -537,8 +566,19 @@ public abstract class Util implements I2PConstants {
}
public static String formatSize(double size) {
return formatSize(size, 0);
}
public static String formatSpeed(double size) {
return formatSize(size, 1);
}
public static String formatSize(double size, int baseScale) {
int scale;
for (scale = 0; size >= 1024.0D; size /= 1024.0D) {
for (int i = 0; i < baseScale; i++) {
size /= 1024.0D;
}
for (scale = baseScale; size >= 1024.0D; size /= 1024.0D) {
++scale;
}

View File

@ -1,10 +1,12 @@
package net.i2p.android.router.web;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.View;
import android.webkit.HttpAuthHandler;
@ -29,6 +31,7 @@ import java.io.OutputStream;
public class I2PWebViewClient extends WebViewClient {
private Fragment _parentFrag;
private BGLoad _lastTask;
/** save it here so we can dismiss it in onPageFinished() */
private ProgressDialog _lastDialog;
@ -40,6 +43,11 @@ public class I2PWebViewClient extends WebViewClient {
private static final String ERROR_URL = "<p>Unable to load URL: ";
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
public I2PWebViewClient(Fragment parentFrag) {
super();
_parentFrag = parentFrag;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Util.d("Should override? " + url);
@ -102,7 +110,7 @@ public class I2PWebViewClient extends WebViewClient {
///////// API 8
// Otherwise hangs waiting for CSS
view.getSettings().setBlockNetworkLoads(false);
_lastDialog = new ProgressDialog(view.getContext());
_lastDialog = new ProgressDialog(_parentFrag.getContext());
BGLoad task = new BackgroundEepLoad(view, h, _lastDialog);
_lastTask = task;
task.execute(url);
@ -215,10 +223,12 @@ public class I2PWebViewClient extends WebViewClient {
private abstract static class BGLoad extends AsyncTask<String, Integer, Integer> implements DialogInterface.OnCancelListener {
protected final WebView _view;
protected final Context _ctx;
protected final ProgressDialog _dialog;
public BGLoad(WebView view, ProgressDialog dialog) {
_view = view;
_ctx = view.getContext();
if (dialog != null)
dialog.setCancelable(true);
_dialog = dialog;
@ -305,9 +315,9 @@ public class I2PWebViewClient extends WebViewClient {
protected Integer doInBackground(String... urls) {
final String url = urls[0];
Uri uri = Uri.parse(url);
File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri);
File cacheFile = AppCache.getInstance(_ctx).getCacheFile(uri);
if (cacheFile.exists()) {
final Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
final Uri resUri = AppCache.getInstance(_ctx).getCacheUri(uri);
Util.d("Loading " + url + " from resource cache " + resUri);
_view.post(new Runnable() {
@Override
@ -325,7 +335,7 @@ public class I2PWebViewClient extends WebViewClient {
//EepGetFetcher fetcher = new EepGetFetcher(url);
OutputStream out = null;
try {
out = AppCache.getInstance(_view.getContext()).createCacheFile(uri);
out = AppCache.getInstance(_ctx).createCacheFile(uri);
// write error to stream
EepGetFetcher fetcher = new EepGetFetcher(url, out, true);
fetcher.addStatusListener(this);
@ -338,11 +348,11 @@ public class I2PWebViewClient extends WebViewClient {
if (success) {
// store in cache, get content URL, and load that way
// Set as current base
final Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
final Uri content = AppCache.getInstance(_ctx).addCacheFile(uri, true);
if (content != null) {
Util.d("Stored cache in " + content);
} else {
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
AppCache.getInstance(_ctx).removeCacheFile(uri);
Util.d("cache create error");
return 0;
}
@ -381,7 +391,7 @@ public class I2PWebViewClient extends WebViewClient {
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
}
}
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
AppCache.getInstance(_ctx).removeCacheFile(uri);
Util.d("loading error data URL: " + url);
final String finalMsg = msg;
_view.post(new Runnable() {
@ -403,30 +413,35 @@ public class I2PWebViewClient extends WebViewClient {
protected void onProgressUpdate(Integer... progress) {
if (isCancelled())
return;
int prog = progress[0];
if (prog < 0) {
_dialog.setTitle("Contacting...");
_dialog.setMessage(_host);
_dialog.setIndeterminate(true);
_dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
_dialog.setOnCancelListener(this);
_dialog.show();
} else if (prog == 0 && _total > 0) {
_dialog.setTitle("Downloading...");
_dialog.setMessage("...from " + _host);
_dialog.setIndeterminate(false);
_dialog.setMax(_total);
_dialog.setProgress(0);
} else if (_total > 0) {
// so it isn't at 100% while loading images and CSS
_dialog.setProgress(Math.min(prog, _total * 99 / 100));
} else if (prog > 0) {
// ugly, need custom
_dialog.setTitle("Downloading...");
_dialog.setMessage("...from " + _host + ": " + DataHelper.formatSize(prog) + 'B');
//_dialog.setProgress(prog);
} else {
// nothing
try {
int prog = progress[0];
if (prog < 0) {
_dialog.setTitle("Contacting...");
_dialog.setMessage(_host);
_dialog.setIndeterminate(true);
_dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
_dialog.setOnCancelListener(this);
_dialog.show();
} else if (prog == 0 && _total > 0) {
_dialog.setTitle("Downloading...");
_dialog.setMessage("...from " + _host);
_dialog.setIndeterminate(false);
_dialog.setMax(_total);
_dialog.setProgress(0);
} else if (_total > 0) {
// so it isn't at 100% while loading images and CSS
_dialog.setProgress(Math.min(prog, _total * 99 / 100));
} else if (prog > 0) {
// ugly, need custom
_dialog.setTitle("Downloading...");
_dialog.setMessage("...from " + _host + ": " + DataHelper.formatSize(prog) + 'B');
//_dialog.setProgress(prog);
} else {
// nothing
}
} catch (IllegalArgumentException iae) {
// throws IAE - not attached to window manager - perhaps due to screen rotation?
Util.e("Error while updating I2PWebViewClient dialog", iae);
}
}

View File

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

View File

@ -44,24 +44,26 @@ public class WebFragment extends I2PFragmentBase {
TextView tv = (TextView) v.findViewById(R.id.browser_status);
tv.setText(WARNING);
WebView wv = (WebView) v.findViewById(R.id.browser_webview);
_wvClient = new I2PWebViewClient();
_wvClient = new I2PWebViewClient(this);
wv.setWebViewClient(_wvClient);
wv.getSettings().setBuiltInZoomControls(true);
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
wv.getSettings().setUseWideViewPort(true);
_uriStr = getArguments().getString(HTML_URI);
if (_uriStr != null) {
Uri uri = Uri.parse(_uriStr);
//wv.getSettings().setLoadsImagesAutomatically(true);
//wv.loadUrl(uri.toString());
// go thru the client so .i2p will work too
_wvClient.shouldOverrideUrlLoading(wv, uri.toString());
} else {
wv.getSettings().setLoadsImagesAutomatically(false);
int id = getArguments().getInt(HTML_RESOURCE_ID, 0);
// no default, so restart should keep previous view
if (id != 0)
loadResource(wv, id);
if (getArguments() != null) {
_uriStr = getArguments().getString(HTML_URI);
if (_uriStr != null) {
Uri uri = Uri.parse(_uriStr);
//wv.getSettings().setLoadsImagesAutomatically(true);
//wv.loadUrl(uri.toString());
// go thru the client so .i2p will work too
_wvClient.shouldOverrideUrlLoading(wv, uri.toString());
} else {
wv.getSettings().setLoadsImagesAutomatically(false);
int id = getArguments().getInt(HTML_RESOURCE_ID, 0);
// no default, so restart should keep previous view
if (id != 0)
loadResource(wv, id);
}
}
return v;
}

View File

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

View File

@ -30,10 +30,10 @@ import java.util.List;
* To create an actual wizard model, extend this class and implement {@link #onNewRootPageList()}.
*/
public abstract class AbstractWizardModel implements ModelCallbacks {
protected Context mContext;
protected final Context mContext;
private List<ModelCallbacks> mListeners = new ArrayList<ModelCallbacks>();
private PageList mRootPageList;
private final List<ModelCallbacks> mListeners = new ArrayList<ModelCallbacks>();
private final PageList mRootPageList;
public AbstractWizardModel(Context context) {
mContext = context;
@ -67,7 +67,10 @@ public abstract class AbstractWizardModel implements ModelCallbacks {
public void load(Bundle savedValues) {
for (String key : savedValues.keySet()) {
mRootPageList.findByKey(key).resetData(savedValues.getBundle(key));
// Expanded the code to hunt NPE - Ticket #2389
Page tmp = mRootPageList.findByKey(key);
Bundle tmpBundle = savedValues.getBundle(key);
tmp.resetData(tmpBundle);
}
}

View File

@ -31,6 +31,7 @@ public class SingleTextFieldPage extends Page {
protected String mDef = null;
protected String mDesc = "";
protected boolean mNumeric = false;
private String mFeedback;
public SingleTextFieldPage(ModelCallbacks callbacks, String title) {
super(callbacks, title);
@ -81,14 +82,24 @@ public class SingleTextFieldPage extends Page {
// Override these in subclasses to add content verification.
public boolean isValid() {
if (mNumeric) {
try {
//noinspection ResultOfMethodCallIgnored
Integer.parseInt(mData.getString(SIMPLE_DATA_KEY));
} catch (NumberFormatException e) {
mFeedback = "Not a number";
return false;
}
}
mFeedback = "";
return true;
}
public boolean showFeedback() {
return false;
return true;
}
public String getFeedback() {
return "";
return mFeedback;
}
}

View File

@ -46,8 +46,12 @@ public abstract class AbstractWizardActivity extends FragmentActivity implements
// Create the WizardModel before super.onCreate() in case a Fragment
// is created and tries to call e.g. onGetPage()
mWizardModel = onCreateModel();
if (savedInstanceState != null)
mWizardModel.load(savedInstanceState.getBundle("model"));
if (savedInstanceState != null) {
Bundle model = savedInstanceState.getBundle("model");
if (model != null) {
mWizardModel.load(model);
}
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wizard);

View File

@ -190,6 +190,10 @@ public class I2PB64DestinationFragment extends Fragment {
Util.e("Could not find B64 file", fnfe);
Toast.makeText(getActivity(), "Could not find B64 file.",
Toast.LENGTH_SHORT).show();
} catch (SecurityException se) {
Util.e("Could not open B64 file", se);
Toast.makeText(getActivity(), "Could not open B64 file.",
Toast.LENGTH_SHORT).show();
} finally {
if (br != null)
try {

View File

@ -0,0 +1,50 @@
package net.i2p.router.client;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import net.i2p.client.DomainSocketFactory;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
* Unix domain socket version of ClientListenerRunner.
*
* @author str4d
* @since 0.9.14
*/
public class DomainClientListenerRunner extends ClientListenerRunner {
private final DomainSocketFactory factory;
private final Log _log;
public DomainClientListenerRunner(RouterContext context, ClientManager manager) {
super(context, manager, -1);
factory = new DomainSocketFactory(_context);
_log = context.logManager().getLog(getClass());
}
/**
* @throws IOException
*/
@Override
protected ServerSocket getServerSocket() throws IOException {
return factory.createServerSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
}
@Override
public void stopListening() {
_running = false;
// LocalServerSocket.close() fails silently if the socket is blocking in accept(), so we
// trick the socket by opening a new connection and then immediately closing it.
// http://stackoverflow.com/questions/8007982/java-serversocket-and-android-localserversocket
try {
_log.debug("Connecting to domain socket to trigger close");
Socket s = factory.createSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
s.close();
} catch (IOException e) {
_log.error("Failed to connect to domain socket to trigger close", e);
}
// runServer() will close the LocalServerSocket.
}
}

View File

@ -1,162 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2013 Mohammed Lakkadshaw
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.htmltextview;
import java.util.Vector;
import org.xml.sax.XMLReader;
import android.text.Editable;
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
import android.text.style.AlignmentSpan;
import android.text.style.BulletSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.TypefaceSpan;
import android.util.Log;
/**
* Some parts of this code are based on android.text.Html
*/
public class HtmlTagHandler implements Html.TagHandler {
private int mListItemCount = 0;
private Vector<String> mListParents = new Vector<String>();
private static class Code {
}
private static class Center {
}
@Override
public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
if (opening) {
// opening tag
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "opening, output: " + output.toString());
}
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) {
mListParents.add(tag);
mListItemCount = 0;
} else if (tag.equalsIgnoreCase("code")) {
start(output, new Code());
} else if (tag.equalsIgnoreCase("center")) {
start(output, new Center());
}
} else {
// closing tag
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "closing, output: " + output.toString());
}
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) {
mListParents.remove(tag);
mListItemCount = 0;
} else if (tag.equalsIgnoreCase("li")) {
handleListTag(output);
} else if (tag.equalsIgnoreCase("code")) {
end(output, Code.class, new TypefaceSpan("monospace"), false);
} else if (tag.equalsIgnoreCase("center")) {
end(output, Center.class, new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), true);
}
}
}
/**
* Mark the opening tag by using private classes
*
* @param output
* @param mark
*/
private void start(Editable output, Object mark) {
int len = output.length();
output.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "len: " + len);
}
}
private void end(Editable output, Class kind, Object repl, boolean paragraphStyle) {
Object obj = getLast(output, kind);
// start of the tag
int where = output.getSpanStart(obj);
// end of the tag
int len = output.length();
output.removeSpan(obj);
if (where != len) {
// paragraph styles like AlignmentSpan need to end with a new line!
if (paragraphStyle) {
output.append("\n");
len++;
}
output.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "where: " + where);
Log.d(HtmlTextView.TAG, "len: " + len);
}
}
/**
* Get last marked position of a specific tag kind (private class)
*
* @param text
* @param kind
* @return
*/
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length; i > 0; i--) {
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
return objs[i - 1];
}
}
return null;
}
}
private void handleListTag(Editable output) {
if (mListParents.lastElement().equals("ul")) {
output.append("\n");
String[] split = output.toString().split("\n");
int lastIndex = split.length - 1;
int start = output.length() - split[lastIndex].length() - 1;
output.setSpan(new BulletSpan(15 * mListParents.size()), start, output.length(), 0);
} else if (mListParents.lastElement().equals("ol")) {
mListItemCount++;
output.append("\n");
String[] split = output.toString().split("\n");
int lastIndex = split.length - 1;
int start = output.length() - split[lastIndex].length() - 1;
output.insert(start, mListItemCount + ". ");
output.setSpan(new LeadingMarginSpan.Standard(15 * mListParents.size()), start, output.length(), 0);
}
}
}

View File

@ -1,90 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.htmltextview;
import android.content.Context;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import java.io.InputStream;
public class HtmlTextView extends JellyBeanSpanFixTextView {
public static final String TAG = "HtmlTextView";
public static final boolean DEBUG = false;
public HtmlTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public HtmlTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HtmlTextView(Context context) {
super(context);
}
/**
* http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
*
* @param is
* @return
*/
static private String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
/**
* Loads HTML from a raw resource, i.e., a HTML file in res/raw/.
* This allows translatable resource (e.g., res/raw-de/ for german).
* The containing HTML is parsed to Android's Spannable format and then displayed.
*
* @param context
* @param id for example: R.raw.help
*/
public void setHtmlFromRawResource(Context context, int id, boolean useLocalDrawables) {
// load html from html file from /res/raw
InputStream inputStreamText = context.getResources().openRawResource(id);
setHtmlFromString(convertStreamToString(inputStreamText), useLocalDrawables);
}
/**
* Parses String containing HTML to Android's Spannable format and displays it in this TextView.
*
* @param html String containing HTML, for example: "<b>Hello world!</b>"
*/
public void setHtmlFromString(String html, boolean useLocalDrawables) {
Html.ImageGetter imgGetter;
if (useLocalDrawables) {
imgGetter = new LocalImageGetter(getContext());
} else {
imgGetter = new UrlImageGetter(this, getContext());
}
// this uses Android's Html class for basic parsing, and HtmlTagHandler
setText(Html.fromHtml(html, imgGetter, new HtmlTagHandler()));
// make links work
setMovementMethod(LinkMovementMethod.getInstance());
// no flickering when clicking textview for Android < 4, but overriders color...
// text.setTextColor(getResources().getColor(android.R.color.secondary_text_dark_nodisable));
}
}

View File

@ -1,209 +0,0 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2012 Pierre-Yves Ricau <py.ricau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.htmltextview;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* <p/>
* A {@link android.widget.TextView} that insert spaces around its text spans where needed to prevent
* {@link IndexOutOfBoundsException} in {@link #onMeasure(int, int)} on Jelly Bean.
* <p/>
* When {@link #onMeasure(int, int)} throws an exception, we try to fix the text by adding spaces
* around spans, until it works again. We then try removing some of the added spans, to minimize the
* insertions.
* <p/>
* The fix is time consuming (a few ms, it depends on the size of your text), but it should only
* happen once per text change.
* <p/>
* See http://code.google.com/p/android/issues/detail?id=35466
*/
public class JellyBeanSpanFixTextView extends TextView {
private static class FixingResult {
public final boolean fixed;
public final List<Object> spansWithSpacesBefore;
public final List<Object> spansWithSpacesAfter;
public static FixingResult fixed(List<Object> spansWithSpacesBefore,
List<Object> spansWithSpacesAfter) {
return new FixingResult(true, spansWithSpacesBefore, spansWithSpacesAfter);
}
public static FixingResult notFixed() {
return new FixingResult(false, null, null);
}
private FixingResult(boolean fixed, List<Object> spansWithSpacesBefore,
List<Object> spansWithSpacesAfter) {
this.fixed = fixed;
this.spansWithSpacesBefore = spansWithSpacesBefore;
this.spansWithSpacesAfter = spansWithSpacesAfter;
}
}
public JellyBeanSpanFixTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public JellyBeanSpanFixTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public JellyBeanSpanFixTextView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} catch (IndexOutOfBoundsException e) {
fixOnMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
/**
* If possible, fixes the Spanned text by adding spaces around spans when needed.
*/
private void fixOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
CharSequence text = getText();
if (text instanceof Spanned) {
SpannableStringBuilder builder = new SpannableStringBuilder(text);
fixSpannedWithSpaces(builder, widthMeasureSpec, heightMeasureSpec);
} else {
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "The text isn't a Spanned");
}
fallbackToString(widthMeasureSpec, heightMeasureSpec);
}
}
/**
* Add spaces around spans until the text is fixed, and then removes the unneeded spaces
*/
private void fixSpannedWithSpaces(SpannableStringBuilder builder, int widthMeasureSpec,
int heightMeasureSpec) {
long startFix = System.currentTimeMillis();
FixingResult result = addSpacesAroundSpansUntilFixed(builder, widthMeasureSpec,
heightMeasureSpec);
if (result.fixed) {
removeUnneededSpaces(widthMeasureSpec, heightMeasureSpec, builder, result);
} else {
fallbackToString(widthMeasureSpec, heightMeasureSpec);
}
if (HtmlTextView.DEBUG) {
long fixDuration = System.currentTimeMillis() - startFix;
Log.d(HtmlTextView.TAG, "fixSpannedWithSpaces() duration in ms: " + fixDuration);
}
}
private FixingResult addSpacesAroundSpansUntilFixed(SpannableStringBuilder builder,
int widthMeasureSpec, int heightMeasureSpec) {
Object[] spans = builder.getSpans(0, builder.length(), Object.class);
List<Object> spansWithSpacesBefore = new ArrayList<Object>(spans.length);
List<Object> spansWithSpacesAfter = new ArrayList<Object>(spans.length);
for (Object span : spans) {
int spanStart = builder.getSpanStart(span);
if (isNotSpace(builder, spanStart - 1)) {
builder.insert(spanStart, " ");
spansWithSpacesBefore.add(span);
}
int spanEnd = builder.getSpanEnd(span);
if (isNotSpace(builder, spanEnd)) {
builder.insert(spanEnd, " ");
spansWithSpacesAfter.add(span);
}
try {
setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
return FixingResult.fixed(spansWithSpacesBefore, spansWithSpacesAfter);
} catch (IndexOutOfBoundsException notFixed) {
}
}
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "Could not fix the Spanned by adding spaces around spans");
}
return FixingResult.notFixed();
}
private boolean isNotSpace(CharSequence text, int where) {
return where < 0 || text.charAt(where) != ' ';
}
private void setTextAndMeasure(CharSequence text, int widthMeasureSpec, int heightMeasureSpec) {
setText(text);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void removeUnneededSpaces(int widthMeasureSpec, int heightMeasureSpec,
SpannableStringBuilder builder, FixingResult result) {
for (Object span : result.spansWithSpacesAfter) {
int spanEnd = builder.getSpanEnd(span);
builder.delete(spanEnd, spanEnd + 1);
try {
setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
} catch (IndexOutOfBoundsException ignored) {
builder.insert(spanEnd, " ");
}
}
boolean needReset = true;
for (Object span : result.spansWithSpacesBefore) {
int spanStart = builder.getSpanStart(span);
builder.delete(spanStart - 1, spanStart);
try {
setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
needReset = false;
} catch (IndexOutOfBoundsException ignored) {
needReset = true;
int newSpanStart = spanStart - 1;
builder.insert(newSpanStart, " ");
}
}
if (needReset) {
setText(builder);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private void fallbackToString(int widthMeasureSpec, int heightMeasureSpec) {
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "Fallback to unspanned text");
}
String fallbackText = getText().toString();
setTextAndMeasure(fallbackText, widthMeasureSpec, heightMeasureSpec);
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 drawk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.htmltextview;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.util.Log;
/**
* Copied from http://stackoverflow.com/a/22298833
*/
public class LocalImageGetter implements Html.ImageGetter {
Context c;
public LocalImageGetter(Context c) {
this.c = c;
}
public Drawable getDrawable(String source) {
int id = c.getResources().getIdentifier(source, "drawable", c.getPackageName());
if (id == 0) {
// the drawable resource wasn't found in our package, maybe it is a stock android drawable?
id = c.getResources().getIdentifier(source, "drawable", "android");
}
if (id == 0) {
// prevent a crash if the resource still can't be found
Log.e(HtmlTextView.TAG, "source could not be found: " + source);
return null;
} else {
Drawable d = c.getResources().getDrawable(id);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d;
}
}
}

View File

@ -1,124 +0,0 @@
/*
* Copyright (C) 2013 Antarix Tandon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.htmltextview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.text.Html.ImageGetter;
import android.view.View;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
public class UrlImageGetter implements ImageGetter {
Context c;
View container;
/**
* Construct the URLImageParser which will execute AsyncTask and refresh the container
*
* @param t
* @param c
*/
public UrlImageGetter(View t, Context c) {
this.c = c;
this.container = t;
}
public Drawable getDrawable(String source) {
UrlDrawable urlDrawable = new UrlDrawable();
// get the actual source
ImageGetterAsyncTask asyncTask = new ImageGetterAsyncTask(urlDrawable);
asyncTask.execute(source);
// return reference to URLDrawable which will asynchronously load the image specified in the src tag
return urlDrawable;
}
public class ImageGetterAsyncTask extends AsyncTask<String, Void, Drawable> {
UrlDrawable urlDrawable;
public ImageGetterAsyncTask(UrlDrawable d) {
this.urlDrawable = d;
}
@Override
protected Drawable doInBackground(String... params) {
String source = params[0];
return fetchDrawable(source);
}
@Override
protected void onPostExecute(Drawable result) {
// set the correct bound according to the result from HTTP call
urlDrawable.setBounds(0, 0, result.getIntrinsicWidth(), result.getIntrinsicHeight());
// change the reference of the current drawable to the result from the HTTP call
urlDrawable.drawable = result;
// redraw the image by invalidating the container
UrlImageGetter.this.container.invalidate();
}
/**
* Get the Drawable from URL
*
* @param urlString
* @return
*/
public Drawable fetchDrawable(String urlString) {
try {
InputStream is = fetch(urlString);
Drawable drawable = Drawable.createFromStream(is, "src");
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
return drawable;
} catch (Exception e) {
return null;
}
}
private InputStream fetch(String urlString) throws MalformedURLException, IOException {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet request = new HttpGet(urlString);
HttpResponse response = httpClient.execute(request);
return response.getEntity().getContent();
}
}
@SuppressWarnings("deprecation")
public class UrlDrawable extends BitmapDrawable {
protected Drawable drawable;
@Override
public void draw(Canvas canvas) {
// override the draw to facilitate refresh function later
if (drawable != null) {
drawable.draw(canvas);
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

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