Compare commits
317 Commits
android-0.
...
i2p-2.3.0
Author | SHA1 | Date | |
---|---|---|---|
41ca3fe527 | |||
c5468e4829 | |||
174bb7f827 | |||
3d92f32c13 | |||
2fae27a3b3 | |||
71d38c64e0 | |||
ddb651a602 | |||
a8ad1d8d47 | |||
912602bfc0 | |||
24e3358ffa | |||
fcc51c429d | |||
de0ee87db3 | |||
2102d55315 | |||
2f900ebe2d | |||
5c03981a61 | |||
c68f0ff23a | |||
b01f44ae1f | |||
05aeb0cf37 | |||
fd8d596064 | |||
ea3fe136e4 | |||
47f4530f6f | |||
016a7a47fd | |||
f8410deeba | |||
7268132ddd | |||
62545f1247 | |||
99b6992b5c | |||
e8ebb0a569 | |||
3ab2c83b7b | |||
cb46abca82 | |||
63b345e329 | |||
794b8433d8 | |||
a7891a3674 | |||
7076785ec1 | |||
f8eda45409 | |||
8d0f1689c1 | |||
25ef37cddf | |||
c5936858a2 | |||
17d2e2fc93 | |||
a0e41e5171 | |||
2d1244f339 | |||
9eec1d3348 | |||
5824fd4efa | |||
9c87bfcb51 | |||
0c380f2ef5 | |||
dafe3d0002 | |||
255ac60691 | |||
23f681dd7d | |||
d6a55a122a | |||
454937d345 | |||
ec8cf76fc4 | |||
6788f9a663 | |||
dc96c6251f | |||
c2bced68bb | |||
e21b4e5cbd | |||
1f434d8a05 | |||
cd12d84d47 | |||
82b92caa55 | |||
49e4fcc49c | |||
a9abe05325 | |||
ea6705374f | |||
3e28f2bf93 | |||
4c881525f9 | |||
9280c80a53 | |||
e465a6d232 | |||
d7f79e7b0b | |||
68f44b8ec1 | |||
0835a42fbd | |||
78e649dc9b | |||
84e73a693c | |||
c0ad7dfc09 | |||
dec68432bc | |||
f93fc155ef | |||
e348af340a | |||
aa3631ed33 | |||
f2088ad1e5 | |||
008e9b7961 | |||
90a46bcc3f | |||
f69f748064 | |||
640a2e1918 | |||
8aead91700 | |||
b18a2e6241 | |||
09c3e6e12c | |||
74bf0eade2 | |||
2fa6e7f3cb | |||
24b741be1e | |||
ac8ce6f916 | |||
cd7d0ad723 | |||
c5a0c3608d | |||
3863c0e183 | |||
69ea3d8bde | |||
7cafe6da48 | |||
9464b46ad0 | |||
c230a5a101 | |||
3d37f2ae07 | |||
f88aafe292 | |||
aa2dab1d3c | |||
8e85eaa2f0 | |||
6a1848caf6 | |||
aa36b4cb14 | |||
78d4b12142 | |||
c2f3a80dec | |||
a679784aab | |||
835667437b | |||
f016edec7a | |||
1e3b517219 | |||
f4e3b15fcf | |||
fc4154be67 | |||
5bf0b18767 | |||
eb5ef3129b | |||
6e87d248c0 | |||
c9b0aff142 | |||
5acac0dbc4 | |||
0b42a7ee64 | |||
c3a798ee3d | |||
e682369311 | |||
14b953f145 | |||
036c807d6b | |||
a41fca95df | |||
26fdf40f25 | |||
070af6529c | |||
7ba0892351 | |||
5b9cdb9f9f | |||
b79d39a74a | |||
5fc5aed0c9 | |||
aec25ab374 | |||
9d5c495936 | |||
4dc2bb6b01 | |||
373e013911 | |||
83bb7096a7 | |||
cc1c4690a2 | |||
69ad581235 | |||
4be227631d | |||
3f3f1f8e3d | |||
22290da1a4 | |||
7caf21d552 | |||
701860a525 | |||
7615aca89e | |||
cae8ed2ae3 | |||
a523e1cb4a | |||
5e048af9c1 | |||
77b6c4d30f | |||
983a94e1c4 | |||
1f79323d66 | |||
d86d3ad5ae | |||
c41b064045 | |||
4c299ecda3 | |||
761f427366 | |||
f4c4bfe8be | |||
7f15a6f1e1 | |||
5734760d58 | |||
3244adfcd2 | |||
ce62b0fb97 | |||
64673ee185 | |||
a36cabdcc8 | |||
1e8531c731 | |||
0935659d6d | |||
bef5f7e746 | |||
d64e8359c1 | |||
5fd77ea62d | |||
8626ac2913 | |||
1258f18bc3 | |||
8b677abd3c | |||
f3d1e89002 | |||
f39c9a0fc4 | |||
3186caa6bb | |||
fc1259d8a8 | |||
848d07331e | |||
9794d26d0c | |||
76cd9c85ef | |||
d4c7c480fb | |||
00aa80d104 | |||
20086685aa | |||
86a8effd82 | |||
55d6e6d24e | |||
3b05046df3 | |||
b8587cd0ab | |||
61dd550040 | |||
478ff63889 | |||
e747619b85 | |||
f043bb3d72 | |||
98d8106f56 | |||
74eb0ce4f6 | |||
d79813d6d1 | |||
8f60c6ce9e | |||
7e0d017858 | |||
8470435ee2 | |||
5023d69222 | |||
34c7464f5b | |||
2da6fe9c62 | |||
1927c9e5a3 | |||
1ace085d13 | |||
95e6c1f7a6 | |||
03bdd575a7 | |||
98c5313d75 | |||
c047bdf085 | |||
e113ef0002 | |||
3e9b47307d | |||
2d864ad8b1 | |||
5b7f9bd452 | |||
3c89749f94 | |||
edbd5fd7ea | |||
faf8bf74af | |||
851e774e7a | |||
dcea801116 | |||
cb5235e6da | |||
5c7eaf2484 | |||
764cfc91ec | |||
177a2c6dc1 | |||
4dccd1dbae | |||
50141eb24b | |||
4dab632bc8 | |||
350515041a | |||
5ba294c2c2 | |||
a215363206 | |||
4bd647c67c | |||
b951892c05 | |||
16e05e0dd8 | |||
5bdf119b81 | |||
b73b72c9c8 | |||
0bae211da5 | |||
709392e8b6 | |||
80ed1e71da | |||
6c0a60892f | |||
7f13aa26fb | |||
7167a11844 | |||
1f140bf95a | |||
940b2b83a1 | |||
bdbebe11c4 | |||
51ca137102 | |||
f0ff4eeab7 | |||
93d103e5ad | |||
b87d77d5e3 | |||
ffbd8cfb76 | |||
2d1664574d | |||
5d3aa1f625 | |||
3fa53c7654 | |||
84ecf55ff8 | |||
72ad40ecfc | |||
2f48898235 | |||
39758c8cf4 | |||
ecc5509007 | |||
0e75b3e957 | |||
7b4c80216d | |||
70bbc18054 | |||
09fcef23a4 | |||
333f09073a | |||
58cb33aa77 | |||
9654fa24cc | |||
7843b37a7e | |||
1db9128afc | |||
c03d3a8b92 | |||
80b7455602 | |||
1fcf5aa49b | |||
640803418d | |||
56fa0b0302 | |||
9c10eef0e3 | |||
9fd5e43115 | |||
d5bd9b8eaa | |||
5b1203a1c6 | |||
fbe79eee2e | |||
ffa21fc1e0 | |||
5faf1f5bb0 | |||
e15efb6537 | |||
cd1702d53c | |||
5a6ca8a0a4 | |||
82d184cf90 | |||
5162bb604b | |||
3aff2a7a9d | |||
cae565761d | |||
c2b6cee9a2 | |||
84e7b1f41c | |||
7ebed1e6d2 | |||
2e68122b8d | |||
899a3f4cfc | |||
52d49a4ab6 | |||
75f705125f | |||
722ddf8a47 | |||
524d21631e | |||
86e6060217 | |||
b5c7fad876 | |||
5f3ca0fe69 | |||
ddd9bea786 | |||
6aac99e7ea | |||
da763a7c81 | |||
6cb46c3168 | |||
610e963d22 | |||
96b8ed43e0 | |||
812c28cd33 | |||
3f7312653a | |||
c89d3992c7 | |||
d9394685c9 | |||
b140158b24 | |||
4f5b0bd21a | |||
1d17d89ccb | |||
b6074da7c4 | |||
c0fdb4aff7 | |||
e00c9cc449 | |||
423ca46672 | |||
66ed9d94a7 | |||
a68ef9d372 | |||
cb389123c5 | |||
62cca0ed50 | |||
c99e3c0b41 | |||
302c51ccfa | |||
1e34bc2159 | |||
24f6f4789d | |||
2de11a4067 | |||
d83a2f9919 | |||
bf36b4c2e6 | |||
daa0b739a3 | |||
5e1b0d9b50 | |||
917742847a | |||
9460e3202f | |||
5f388a7c6b | |||
39d5de7eb4 | |||
0fb1ef881c | |||
8230769191 |
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
app/pkg-temp
|
||||
app/build
|
||||
app/pkg-mavencentral
|
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
etc/docker.signing.properties
|
||||
signing.properties
|
||||
.idea
|
||||
.idea/
|
||||
.gradle/
|
||||
local.properties
|
||||
override.properties
|
||||
build
|
52
.mtn-ignore
@ -1,52 +0,0 @@
|
||||
# Just to try and prevent some noob disasters.
|
||||
# Use mtn add --no-respect-ignore foo.jar to ignore this ignore list
|
||||
_jsp\.java$
|
||||
\.bz2$
|
||||
\.class$
|
||||
\.diff$
|
||||
\.exe$
|
||||
\.fba$
|
||||
\.gz$
|
||||
\.jar$
|
||||
\.out$
|
||||
\.patch$
|
||||
\.sig$
|
||||
\.sud$
|
||||
\.su2$
|
||||
\.tar$
|
||||
\.war$
|
||||
\.zip$
|
||||
^\.
|
||||
^build/
|
||||
^pkg-temp/
|
||||
~$
|
||||
/build/
|
||||
/classes/
|
||||
|
||||
# Android-specific ignores
|
||||
^lib/client/libs
|
||||
^routerjars/libs
|
||||
local.properties
|
||||
signing.properties
|
||||
|
||||
#IntelliJ IDEA
|
||||
^.idea
|
||||
.*.iml
|
||||
.*.ipr
|
||||
.*.iws
|
||||
|
||||
#Gradle
|
||||
build
|
||||
|
||||
# I2P-specific ignores
|
||||
^app/src/main/res/drawable/i2plogo.png
|
||||
^app/src/main/res/raw/blocklist_txt
|
||||
^app/src/main/res/raw/hosts_txt
|
||||
^app/src/main/res/raw/.*_ht
|
||||
^app/src/main/res/raw/license_
|
||||
^app/src/main/res/raw/certificates_zip
|
||||
^app/src/main/assets/themes/console/images
|
||||
^app/src/main/assets/themes/console/light/console.css
|
||||
^app/src/main/assets/themes/console/light/images/header.png
|
||||
^scripts/build.number
|
||||
^scripts/version.properties
|
28
.tx/config
@ -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, zh_TW: zh-rTW
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, yi: ji, zh_CN: zh, zh_TW: zh-rTW, id: in, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, he: iw
|
||||
|
||||
[I2P.android]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 50
|
||||
|
||||
[I2P.android_lib_helper]
|
||||
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
|
||||
source_file = lib/helper/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
[o:otf:p:I2P:r:android]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 22
|
||||
|
||||
[o:otf:p:I2P:r:android_lib_helper]
|
||||
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
|
||||
source_file = lib/helper/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 50
|
||||
|
||||
|
182
CHANGELOG
@ -1,3 +1,185 @@
|
||||
2.2.0
|
||||
* Add blocklist feed support
|
||||
* Fix translations on stats page
|
||||
* Various bugfixes
|
||||
|
||||
2.1.0-1
|
||||
* adds support for adding base32 addresses to the local addressbook
|
||||
* adds support for destinations with ports in the string to i2ptunnel
|
||||
* adds support for remote applications launching I2P, via MR#3 from @RyeMantis
|
||||
|
||||
2.1.0
|
||||
* Upgrades router to version 2.1.0
|
||||
* Adds jcenter repository back to app/ target, removes jcenter from other targets
|
||||
* explicitly set android.useAndroidX
|
||||
|
||||
2.0.1
|
||||
* Fixes gitlab#49 by applying MUTABLE or IMMUTABLE flags across all PendingIntents
|
||||
|
||||
2.0.0
|
||||
* Updates router to version 2.0.0
|
||||
* Updates gradle plugin
|
||||
* Remove Firefox from supported browsers list due to lack of about:config support
|
||||
* Add Privacy Browser to supported browsers list and write guide for it
|
||||
|
||||
1.9.1
|
||||
* Switches back to mavenCentral builds
|
||||
* Fixes build process for F-Droid main
|
||||
* Using tag i2p.i2p:i2p-android-1.9.0
|
||||
* Allow b32 and blinded b32 to validate in I2PTunnel UI
|
||||
|
||||
1.9.0
|
||||
* Updates router to version 1.9.0
|
||||
* Using tag i2p.i2p:i2p-android-1.9.0
|
||||
|
||||
1.8.2
|
||||
* This update fixes a bug where Family Keys are incorrectly handled by the Android router, leading
|
||||
to a crash.
|
||||
* This update fixes a bug wherein incorrect type-checking caused a crash when using the Network
|
||||
Config page for some users.
|
||||
|
||||
1.8.1
|
||||
* This update fixes a bug where certificates were unpacked to the wrong location on new installs,
|
||||
resulting in the router failing to start.
|
||||
* Decouples the Android version from the I2P library version to allow for android point
|
||||
releases with less pain.
|
||||
|
||||
1.8.0
|
||||
* This release updates the underlying I2P libraries to I2P version 1.8.0
|
||||
* The SAM API now operates in "Interactive Authentication" mode. Users will be prompted via
|
||||
the notificationArea to approve or ignore new SAM connections initiated by external apps.
|
||||
* The network config menu now features a drop-down configuration instead of a slider configuration
|
||||
(since reversed)
|
||||
|
||||
1.7.1
|
||||
* This release updates the underlying I2P libraries to I2P version 1.7.1
|
||||
|
||||
1.7.0
|
||||
* This release updates the underlying I2P libraries to I2P version 1.7.0
|
||||
* Refactor of the unzipResourceToDir function
|
||||
|
||||
1.6.0
|
||||
* This release updates the underlying I2P libraries to I2P version 1.6.0
|
||||
|
||||
1.5.0
|
||||
* This release updates the underlying I2P libraries to I2P version 1.5.0
|
||||
|
||||
0.9.50 2021-05-18
|
||||
* This release updates the underlying I2P libraries to I2P version 0.9.50
|
||||
|
||||
0.9.48 2020-12-02
|
||||
* This release updates the underlying I2P libraries to I2P version 0.9.48
|
||||
* Updates to browser configuration documentation
|
||||
|
||||
0.9.47-1 2020-10-29
|
||||
* This release fixes a number of bugs arising from a misconfigured bootclasspath
|
||||
* Fix a battery-management issue arising on some phones
|
||||
|
||||
0.9.47 / 2020-08-26
|
||||
* Notification bug-fixes on platforms >8.0
|
||||
|
||||
0.9.46 / 2020-06-03
|
||||
* catch ActivityNotFound exception in MainActivity
|
||||
|
||||
0.9.45 / 2020-03-07
|
||||
* No significant changes
|
||||
|
||||
0.9.44 / 2019-12-03
|
||||
* Updated translations
|
||||
* Bumped target sdk version to 28, enforced by google
|
||||
|
||||
0.9.43-1 / 2019-10-28
|
||||
* Fix crash at startup in TCG
|
||||
|
||||
0.9.43 / 2019-10-27
|
||||
* Save state in background thread (tickets #2595, #2632)
|
||||
* Fix ISE in language dialog (ticket #2631)
|
||||
* Fix NPE in create tunnel (ticket #2629)
|
||||
* Update logo after router killed in background
|
||||
* Fix message in tunnels tabs when tunnels not up yet
|
||||
* Hide tunnel actions while TCG is starting
|
||||
* Add battery permissions dialog (ticket #2607)
|
||||
|
||||
0.9.42 / 2019-08-28
|
||||
* Possible fix for tunnel edit dialog crash (ticket #2598)
|
||||
|
||||
0.9.41 / 2019-07-03
|
||||
* New 64 bit libjbigi (ticket #2503)
|
||||
* Update 32 bit jbigi to GMP 6.1.2
|
||||
* Fix for client tunnels not starting after reseed
|
||||
* UPnP Fixes (ticket #2499)
|
||||
* WebView crash fixes (ticket #2390)
|
||||
* i2ptunnel crash fix (ticket #2552)
|
||||
* i2ptunnel ANR fix (ticket #2491)
|
||||
* Browser help pages improvements (tickets #2521, 2523)
|
||||
* Code cleanups and fixes
|
||||
* Update visibility of floating menu items
|
||||
* Put our router info at top of list
|
||||
* Table layout cleanups
|
||||
* Fixes for threads remaining after router stop
|
||||
* Remove BOB
|
||||
* Remove welterde IRC tunnel
|
||||
* Various lint fixes
|
||||
* New translations: Add cs, da, el
|
||||
* Add missing translations to menu: ar, fi, gl, hu, zh_TW
|
||||
|
||||
0.9.40 / 2019-05-10
|
||||
* Open local I2CP socket for 3rd party apps
|
||||
* Fix News URLs
|
||||
* Numerous bug fixes (see trac)
|
||||
|
||||
0.9.39 / 2019-03-23
|
||||
* Set App ID
|
||||
|
||||
0.9.38 / 2019-01-27
|
||||
|
||||
0.9.37 / 2018-10-10
|
||||
|
||||
0.9.36 / 2018-08-27
|
||||
|
||||
0.9.35 / 2018-07-03
|
||||
|
||||
0.9.34 / 2018-04-25
|
||||
|
||||
0.9.33 / 2018-02-18
|
||||
* Translation updates
|
||||
|
||||
0.9.32 / 2017-11-28
|
||||
* Fixed "Application Not Responding" error when restarting all tunnels
|
||||
* Fixed crashes when:
|
||||
* opening the console menu
|
||||
* starting the router
|
||||
* starting the router for the first time
|
||||
* viewing tunnels
|
||||
* viewing the addressbook
|
||||
* opening the addressbook menu
|
||||
* configuring addressbook subscriptions
|
||||
* using a configuration wizard
|
||||
* loading a B64 Destination from file
|
||||
* viewing tunnel settings
|
||||
* saving tunnel settings
|
||||
* installing a tunnel's recommended app without a market app
|
||||
* installing a browser without a market app
|
||||
* rotating the screen while using the built-in browser
|
||||
* Added a "sync" icon to more clearly indicate tunnel "starting" status
|
||||
* Updated Firefox browser config instructions for Firefox Quantum
|
||||
* Translation updates
|
||||
|
||||
0.9.31 / 2017-08-19
|
||||
* Fixed various crashes in the Tunnels UI
|
||||
* Updated Firefox browser config instructions
|
||||
* Minor bug fixes
|
||||
* Dependency and translation updates
|
||||
|
||||
0.9.30 / 2017-05-20
|
||||
* Fixed crashes when creating or deleting tunnels, or adding names to the
|
||||
private addressbook
|
||||
* Minor bug fixes
|
||||
* Dependency and translation updates
|
||||
|
||||
0.9.29 / 2017-03-27
|
||||
* Dependency and translation updates
|
||||
|
||||
0.9.28 / 2017-01-02
|
||||
* Bug fixes and translation updates
|
||||
|
||||
|
11
DEVELOPMENT-README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Development Readme
|
||||
|
||||
## How to build development builds of the router core for android?
|
||||
|
||||
Check the RELEASE-PROCESS.md file for general information about how to build and to bump the version.
|
||||
|
||||
In your i2p.i2p codebase checkout, execute `./installer/resources/maven-dev-release.sh` with your build number as first argument.
|
||||
The script locates itself and uses the same codebase as it's in, to produce the maven builds which will be locally installed.
|
||||
|
||||
Next, add the build number to the gradle.properties and build the android build as usual.
|
||||
|
91
DOCKER.md
Normal file
@ -0,0 +1,91 @@
|
||||
Docker Build Instructions
|
||||
=========================
|
||||
|
||||
It is possible to build a container with a pre-installed environment for
|
||||
correctly compiling an I2P for Android development build. Unlike the i2p.i2p
|
||||
container, zero attempt has been made to optimize the size of the container,
|
||||
as it contains a copy of the latest Android SDK, toolchains, and Android NDK,
|
||||
which it must download. To save time, this is cached locally. It is likely to
|
||||
take up to 30 GB of disk space to compile in this way, however, it is very easy
|
||||
and convenient compared to the steps in RELEASE-PROCESS.md and may make
|
||||
building Android reproducibly easier in the future.
|
||||
|
||||
Container dependencies
|
||||
----------------------
|
||||
|
||||
- `menny/android_ndk` (third-party image) (reviewed by idk) (depends on menny/android_sdk
|
||||
- `menny/android_sdk` (third-party image) (reviewed by idk) (depends on ubuntu/18.04)
|
||||
- `ubuntu/18.04` (official docker container) (base container)
|
||||
|
||||
Build the container locally
|
||||
---------------------------
|
||||
|
||||
Run:
|
||||
|
||||
docker build -t i2p.android.base .
|
||||
|
||||
To build the container. It will have a lot to download the first time, so it may take
|
||||
a while to complete.
|
||||
|
||||
Run an Android build in the container
|
||||
-------------------------------------
|
||||
|
||||
Copy the `etc/docker.signing.example.proprties` file to `etc/docker.signing.proprties`,
|
||||
edit it to match your key information and rebuild the container.
|
||||
|
||||
Run:
|
||||
|
||||
docker run -it \
|
||||
-u $(id -u):$(id -g) \
|
||||
--name i2p.android.base \
|
||||
-v $HOME/.gnupg/:/.gnupg/:ro \
|
||||
-v $HOME/.i2p-plugin-keys/:/.i2p-plugin-keys/:ro \
|
||||
-v /run/user/$(id -u)/:/run/user/$(id -u)/:ro \
|
||||
i2p.android.base
|
||||
|
||||
To get the build artifacts for uploading to Maven out of the container, use:
|
||||
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/pkg-mavencentral app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-i2p.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-mstreaming.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-router.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-servlet-i2p.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-streaming.jar app/pkg-mavencentral
|
||||
|
||||
To get the Android build artifacts out of the container, use:
|
||||
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.android.base/app/build/ app/build
|
||||
|
||||
And your android applications will appear in the `app/build` directory, in the same
|
||||
place where non-container builds would go.
|
||||
|
||||
If you encounter a permissions error when rebuilding, delete the `app/build`,
|
||||
`app/pkg-mavencentral` and `app/pkg-temp` path.
|
||||
|
||||
rm -rf app/pkg-temp app/build app/pkg-mavencentral
|
||||
|
||||
Copypasta
|
||||
---------
|
||||
|
||||
Once you have set up builds for the first time, from then on you can update the container and
|
||||
build a fresh set of Maven jars and a new I2P for Android app by copy-pasting the following
|
||||
commands:
|
||||
|
||||
``` sh
|
||||
rm -rf app/pkg-temp app/build app/pkg-mavencentral
|
||||
docker build -t i2p.android.base .
|
||||
docker run -it \
|
||||
-u $(id -u):$(id -g) \
|
||||
--name i2p.android.base \
|
||||
-v $HOME/.gnupg/:/.gnupg/:ro \
|
||||
-v $HOME/.i2p-plugin-keys/:/.i2p-plugin-keys/:ro \
|
||||
-v /run/user/$(id -u)/:/run/user/$(id -u)/:ro \
|
||||
i2p.android.base
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/pkg-mavencentral app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-i2p.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-mstreaming.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-router.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-servlet-i2p.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.i2p/mavencentral-streaming.jar app/pkg-mavencentral
|
||||
docker cp i2p.android.base:/opt/workspace/i2p.android.base/app/build/ app/build
|
||||
```
|
39
Dockerfile
Normal file
@ -0,0 +1,39 @@
|
||||
FROM menny/android_ndk
|
||||
ENV VERSION=0.9.50
|
||||
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
|
||||
RUN echo 'deb http://deb.i2p2.de/ sid main' >> /etc/apt/sources.list
|
||||
RUN echo 'deb-src http://deb.i2p2.de/ sid main' >> /etc/apt/sources.list
|
||||
RUN echo 'deb http://archive.ubuntu.com/ubuntu trusty universe' >> /etc/apt/sources.list
|
||||
RUN wget -O /etc/apt/trusted.gpg.d/i2p-debian-repo.key.asc https://geti2p.net/_static/i2p-debian-repo.key.asc
|
||||
COPY etc/debian-jessie-repo.key.asc /etc/apt/trusted.gpg.d
|
||||
RUN mkdir -p /opt/packages && wget -O /opt/packages/openjdk-7-jre-headless.deb http://security.debian.org/debian-security/pool/updates/main/o/openjdk-7/openjdk-7-jre-headless_7u261-2.6.22-1~deb8u1_amd64.deb
|
||||
RUN apt-get update
|
||||
RUN apt-get build-dep -y i2p i2p-router
|
||||
RUN apt-get install -y ant openjdk-8* libxml2-utils junit4 libhamcrest-java libmockito-java libmaven-ant-tasks-java dpkg-sig maven
|
||||
RUN cd /opt/packages && dpkg-sig -l openjdk-7-jre-headless.deb && dpkg -x openjdk-7-jre-headless.deb /opt/packages/openjdk-7-jre
|
||||
RUN git clone https://github.com/i2p/i2p.i2p --depth=1 -b i2p-$VERSION /opt/workspace/i2p.i2p
|
||||
RUN update-java-alternatives --jre-headless --set java-1.8.0-openjdk-amd64
|
||||
RUN update-java-alternatives --set java-1.8.0-openjdk-amd64
|
||||
RUN update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac
|
||||
RUN update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
|
||||
RUN rm /opt/java/openjdk/ -rfv
|
||||
COPY . /opt/workspace/i2p.android.base
|
||||
COPY etc/docker.local.ndk.properties /opt/workspace/i2p.android.base/client/local.properties
|
||||
COPY etc/docker.local.router.properties /opt/workspace/i2p.android.base/routerjars/local.properties
|
||||
COPY etc/docker.local.sdk.properties /opt/workspace/i2p.android.base/local.properties
|
||||
COPY etc/docker.override.properties /opt/workspace/i2p.android.base/override.properties
|
||||
COPY etc/docker.override.properties /opt/workspace/i2p.i2p/override.properties
|
||||
COPY etc/docker.signing.properties /opt/workspace/i2p.android.base/signing.properties
|
||||
WORKDIR /opt/workspace/i2p.android.base
|
||||
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chown -R 1000:1000
|
||||
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chmod -Rc o+rw
|
||||
RUN find /opt/android-sdk-linux -type d -print0 | xargs -0 chmod -c 0755
|
||||
RUN find /opt/workspace -type d -print0 | xargs -0 chown -R 1000:1000
|
||||
RUN find /opt/workspace -type d -print0 | xargs -0 chmod -Rc o+rw
|
||||
RUN find /opt/workspace -type d -print0 | xargs -0 chmod -c 0755
|
||||
CMD cd /opt/workspace/i2p.i2p && \
|
||||
ant -k mavenCentral; \
|
||||
cp -v *.jar pkg-mavencentral/; \
|
||||
cd /opt/workspace/i2p.android.base && \
|
||||
./gradlew --continue dependencies || true ; \
|
||||
./gradlew --continue assembleRelease; tail -f README.md
|
14
README.md
@ -7,8 +7,8 @@
|
||||
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
- Apache Ant 1.8.0 or higher
|
||||
- I2P source
|
||||
- Android SDK for API 21
|
||||
- Android Build Tools 21.1.2
|
||||
- Android SDK for API 28 or higher
|
||||
- Android Build Tools 28.0.0 or higher
|
||||
- Android Support Repository
|
||||
- Gradle 2.2.1
|
||||
|
||||
@ -67,10 +67,18 @@ systemProp.socksProxyPort=9150
|
||||
```
|
||||
sdk.dir=/path/to/android-studio/sdk
|
||||
```
|
||||
1a. For building with a local router development build:
|
||||
cd ../i2p.i2p
|
||||
installer/resources/maven-dev-release.sh x // x is the build number, e.g. 6
|
||||
cd back here
|
||||
edit gradle.properties, add the build number x to I2P_PROPERTIES=0.9.xx-x
|
||||
|
||||
2. `gradle assembleDebug`
|
||||
|
||||
3. The APK will be placed in `i2p.android.base/app/build/outputs/apk`.
|
||||
3. The APK files will be placed in `i2p.android.base/app/build/outputs/apk` subdirectories.
|
||||
|
||||
4. Install debug build on phone in USB debugging mode
|
||||
adb install app/build/outputs/apk/free/debug/app-free-debug.apk
|
||||
|
||||
### Building with Android Studio
|
||||
|
||||
|
128
RELEASE-PROCESS.md
Normal file
@ -0,0 +1,128 @@
|
||||
# Release Process
|
||||
|
||||
Note to all future maintainers: We have 4 channels that need to be updated in order to have a successful
|
||||
Android release. Many of these channels are updated at different rates, and at times you must wait on a
|
||||
third-party service to complete it's tasks before you may continue. When completing an Android release,
|
||||
keep in mind that you must update 1) Maven 2) Google Play 3) f-droid.i2p.io and 4) F-Droid main
|
||||
repository.
|
||||
|
||||
At the time of this revision, 2020/09/13, the main Android maintainer is idk. idk updates Maven, Google
|
||||
Play, and f-droid.i2p.io, and nextl00p handles working with the F-Droid project to provide an I2P release
|
||||
in their main repository.
|
||||
|
||||
NOTE: The docker container built by the Dockerfile in this repostory ensures that the Pre-requisites and
|
||||
Dependencies are properly met by obtaining them from the Debian package in `oldoldstable` and pre-configuring
|
||||
the override.properties that is used in the Docker container.
|
||||
|
||||
## Tag Freezes and Translations
|
||||
|
||||
1-2 weeks before the software is released, I2P Desktop will have a "Tag Freeze" which is the deadline for
|
||||
translations to be checked in. **When** that time comes, someone who has Transifex privileges should use
|
||||
the command: `tx push -s -R I2P.android` to push any and all changed string resources to transifex for
|
||||
translation.
|
||||
|
||||
**>> Beginning of Docker-enabled Steps <<**
|
||||
|
||||
## Prerequirements
|
||||
|
||||
0. Update the changelog!
|
||||
1. Ensure you have the deprecated maven ant tasks. ( https://maven.apache.org/ant-tasks/download.cgi )
|
||||
2. It should exist at `~/.ant/lib/maven-ant-tasks-2.1.3.jar`
|
||||
3. Ensure you have hamcrest-integration, hamcrest-library, hamcrest-core in the hamcrest.home directory.
|
||||
4. Ensure junit 4.12 at least in junit.home, ensure the jar file is named `junit4.jar`.
|
||||
5. Ensure you have the Mockito framework and accompanying documentation in your $JAVA_HOME
|
||||
6. Ensure to have updated the changelog with the changes done.
|
||||
7. Ensure that you are configured to build i2p.i2p with Java 8. On Debian it is easiest to set with
|
||||
`update-java-alternatives --set java-8-openjdk-amd64` and picking Java 8. **TODO:** add instructions for non-Debian-based
|
||||
systems.
|
||||
8. Ensure that you have a Java 1.7 bootclasspath available. (See **Maven Central** step 2A.)
|
||||
|
||||
## Get all the dependencies ready
|
||||
|
||||
### Maven Central
|
||||
|
||||
1. Check out a clean copy of i2p.i2p at the correct release version. (Make a clean checkout)
|
||||
2. Build the maven packages via `ant mavenCentral` where you end up with mavencentral-*.jar files in the
|
||||
current directory.
|
||||
2. **A)** I2P for Android requires a Java 1.7 bootclasspath, but the servlet jar requires Java 8. So, to do the builds:
|
||||
- First set `javac.compilerargs=-bootclasspath /path/to/java/7/rt.jar:/path/to/java/7/jce.jar` in override.properties
|
||||
- Build with `ant mavenCentral`
|
||||
|
||||
**>> End of Docker-enabled Steps for Maven <<**
|
||||
|
||||
3. Login to http://oss.sonatype.org for uploading the mavencentral-*.jar bundles.
|
||||
4. In nexus, choose "Staging Upload" and upload all of the files with upload mode set to "Artifacts with POM".
|
||||
When uploading the files to nexus, you *must* upload the pom.xml files, and all of their artifacts. For each
|
||||
component, you will need to upload a *.jar, a *.jar.asc, a *sources.jar, a *sources.jar.asc, a javadoc.jar,
|
||||
and a javadoc.jar.asc, and a pom.xml and a pom.xml.asc from the pkg-mavencentral directory during the "Upload
|
||||
Artifacts with POM" operation. You will need to do this once for each component you upload to Nexus.
|
||||
5. Under "Staging Repositories" ensure all where uploaded correctly, select them all and press "Release"
|
||||
in the toolbar.
|
||||
|
||||
#### Example override.properties:
|
||||
|
||||
javac.version=1.7
|
||||
javac.target=1.7
|
||||
javac.source=1.8
|
||||
javac.compilerargs=-bootclasspath /home/user/StudioProjects/java7bootclasspath/rt.jar:/home/user/StudioProjects/java7bootclasspath/jce.jar
|
||||
javac.compilerargs7=-bootclasspath /home/user/StudioProjects/java7bootclasspath/rt.jar:/home/user/StudioProjects/java7bootclasspath/jce.jar
|
||||
build.built-by=name
|
||||
|
||||
### Android Common Build
|
||||
|
||||
Using Docker: in order to use Docker to generate a new Android apk for release, you will
|
||||
need to run the build twice, once for the mavenCentral jars, and once for the actual Android
|
||||
app. After doing the Maven release, follow these steps in the i2p.android.base repository, and re-run
|
||||
the `docker run` step described in `DOCKER.md`
|
||||
|
||||
1. Edit `routerjars/local.properties` to use the clean i2p.i2p copy.
|
||||
2. Pull the latest translations with `tx pull -a -f` and commit them. (If you don't have the `tx` command,
|
||||
do `pip install transifex-client` )
|
||||
- If there are any new translations, `mtn add` them, and add them to `app/src/main/res/values/arrays.xml`
|
||||
(two places, alphabetical order please)
|
||||
3. Ensure that `signing.properties` contains the details of the release key. If you are using Docker, see
|
||||
`DOCKER.md` to perform this step for Docker builds by editing `etc/docker.signing.properties` instead.
|
||||
4. Edit `gradle.properties` to bump the I2P version.
|
||||
5. Edit `app/build.gradle` to bump the Android version number.
|
||||
6. Edit `CHANGELOG` to add the release and date.
|
||||
7. If the helper has changed since the last release, edit
|
||||
`lib/helper/gradle.properties` to bump the version.
|
||||
8. `./gradlew clean assembleRelease`
|
||||
|
||||
### Libraries
|
||||
|
||||
1. `./gradlew :lib:client:uploadArchives`
|
||||
2. If the helper version was changed and should be released: `./gradlew :lib:helper:uploadArchives`
|
||||
3. Check on Sonatype that everything worked, and close/release.
|
||||
|
||||
## Release Packages
|
||||
|
||||
### F-Droid Guide
|
||||
|
||||
This guide is for f-droid.i2p.io, not for F-Droid's main repository. The repository keystore **and** the
|
||||
config.py used to generate the repository are required to complete this process successfully.
|
||||
|
||||
1. Ensure you have the release keys, the keyfile must be placed at `~/.local/share/fdroidserver/keystore.jks`
|
||||
2. If it's the first time, or you have reinstalled anything, ensure `path/to/fdroid/config.py` has correct
|
||||
information.
|
||||
3. Assuming you already have ran `./gradlew clean assembleRelease` from a earlier step, continue.
|
||||
4. `cp app/build/outputs/apk/free/release/app-free-release.apk path/to/fdroid/repo/I2P-VERSION.apk`
|
||||
5. Update `path/to/fdroid/metadata/net.i2p.android.txt` (The versions at the bottom of the file)
|
||||
6. Run `fdroid update` from inside the fdroid path (install fdroid command via `pip install fdroidserver`)
|
||||
7. Zip/tar the local fdroid repo and archive. `rm fdroid.tgz && tar czf fdroid.tgz archive/ repo/` from the
|
||||
fdroid directory.
|
||||
8. Push to download server and put in place. (via SSH for example, `scp fdroid.tgz download.i2p2.de:~/`)
|
||||
9. On the server run `bin-fd/update-fdroid` and `sudo bin-fd/update-app i2p 0.9.40` (This ensures we use the
|
||||
exact same apk file for the download page as in fdroid and gplay)
|
||||
10. Check F-Droid repo works, and app works.
|
||||
|
||||
### Google Play and finishing up
|
||||
|
||||
1. Verify which files that are changed via `mtn ls cha`. It shouldn't be much more than those bellow this
|
||||
line and possible translations (`mtn ls unk`).
|
||||
2. Commit your release changes, `mtn ci gradle.properties lib/helper/gradle.properties app/build.gradle`
|
||||
3. Push free and donate builds to Google Play via https://play.google.com/apps/publish/
|
||||
4. Tag the new release. Example `mtn tag h: android-0.9.36`
|
||||
5. Push the monotone changes. Make sure that they are there at the next git sync.
|
||||
6. Update download page (version and hash, including F-Droid)
|
||||
|
106
app/build.gradle
@ -1,13 +1,16 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
|
||||
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION as String
|
||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
||||
defaultConfig {
|
||||
versionCode 4745235
|
||||
versionName '0.9.28'
|
||||
minSdkVersion 9
|
||||
versionCode 4745279
|
||||
versionName "$I2P_ANDROID_VERSION"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
||||
|
||||
// For Espresso
|
||||
@ -23,28 +26,38 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
applicationIdSuffix '.debug'
|
||||
versionNameSuffix '-DEBUG'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
disable 'MissingDefaultResource'
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'LICENSE.txt'
|
||||
resources {
|
||||
excludes += ['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'
|
||||
}
|
||||
}
|
||||
@ -52,51 +65,38 @@ android {
|
||||
|
||||
dependencies {
|
||||
// Local dependencies
|
||||
compile project(':lib:client')
|
||||
compile project(':lib:helper')
|
||||
compile project(':routerjars')
|
||||
|
||||
implementation project(':lib:client')
|
||||
implementation project(':lib:helper')
|
||||
implementation project(path: ':routerjars', configuration: 'routerjars')
|
||||
// Android Support Repository dependencies
|
||||
def supportVersion = '25.0.1'
|
||||
compile "com.android.support:support-v4:$supportVersion"
|
||||
compile "com.android.support:appcompat-v7:$supportVersion"
|
||||
compile "com.android.support:preference-v7:$supportVersion"
|
||||
compile "com.android.support:preference-v14:$supportVersion"
|
||||
compile "com.android.support:recyclerview-v7:$supportVersion"
|
||||
|
||||
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 'com.androidplot:androidplot-core:0.9.8'
|
||||
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'
|
||||
}
|
||||
compile 'com.pnikosis:materialish-progress:1.7'
|
||||
compile 'net.i2p:router:0.9.28'
|
||||
compile 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||
compile 'org.sufficientlysecure:html-textview:1.6'
|
||||
|
||||
implementation 'com.androidplot:androidplot-core:1.4.1'
|
||||
implementation 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
|
||||
implementation 'com.inkapplications.viewpageindicator:library:2.4.4'
|
||||
implementation 'com.pnikosis:materialish-progress:1.7'
|
||||
implementation "net.i2p:router:$I2P_VERSION"
|
||||
implementation "net.i2p:i2p:$I2P_VERSION"
|
||||
implementation "net.i2p.client:mstreaming:$I2P_VERSION"
|
||||
implementation "net.i2p.client:streaming:$I2P_VERSION"
|
||||
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||
implementation 'org.sufficientlysecure:html-textview:3.1'
|
||||
// Testing-only dependencies
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2') {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:support-v4:50da261acc4ca3d2dea9a43106bf65488711ca97b20a4daa095dba381c205c98',
|
||||
'com.android.support:appcompat-v7:7fead560a22ea4b15848ce3000f312ef611fac0953bf90ca8710a72a1f6e36ea',
|
||||
'com.android.support:preference-v7:d7e3fcb6d5427aa25bfd56c51c24786dbb6f06d998b8b6691e9449e1b11cc205',
|
||||
'com.android.support:preference-v14:9d0269913033d97d8edb29003e1ea19021f2e8f36df4035f819bb948f9a23ed2',
|
||||
'com.android.support:recyclerview-v7:803baba7be537ace8c5cb8a775e37547c22a04c4b028833796c45c26ec1deca2',
|
||||
'com.androidplot:androidplot-core:e44d9e59e06f025330831f7d3c987d2778a3302025184cf0cef05714b5171212',
|
||||
'com.eowise:recyclerview-stickyheaders:7b236da49b33b840e9ba6e7e4182218d1a2d9047236fdbc3ca947352f9b0883b',
|
||||
'com.mcxiaoke.viewpagerindicator:library:1e8aad664137f68abdfee94889f6da3dc98be652a235176a403965a07a25de62',
|
||||
'com.pnikosis:materialish-progress:da089a90d1dab61e9b50038c09081019398f81190d12b0b567ce94b83ef8cf93',
|
||||
'net.i2p:router:de3cf0a0e99823662c938d6a1083f201f8feba7d0ebebaf3179fed7040863b7c',
|
||||
'net.i2p.android.ext:floatingactionbutton:09d43e2d4ac04a91bf7a37e1ec48a8d220204e3a55dca72cd36cd9fa27461ade',
|
||||
'org.sufficientlysecure:html-textview:c409b471618b675e3d2a8588f883c5fe8f3369d00df61ec84b29f29c648370ae',
|
||||
]
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
}
|
||||
|
||||
project.ext.i2pbase = '../i2p.i2p'
|
||||
project.ext.i2pbase = "../i2p.i2p"
|
||||
def Properties props = new Properties()
|
||||
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
||||
|
||||
@ -122,7 +122,7 @@ task copyI2PResources(type: Copy) {
|
||||
outputs.upToDateWhen { false }
|
||||
into 'src/main/res'
|
||||
into('drawable') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/i2plogo.png')
|
||||
}
|
||||
into('raw') {
|
||||
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
|
||||
@ -185,15 +185,15 @@ task copyI2PAssets(type: Copy) {
|
||||
outputs.upToDateWhen { false }
|
||||
into 'src/main/assets/themes/console'
|
||||
into('images') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
|
||||
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/i2plogo.png')
|
||||
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/inbound.png')
|
||||
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/images/outbound.png')
|
||||
}
|
||||
into('light') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
|
||||
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/light/console.css')
|
||||
}
|
||||
into('light/images') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
|
||||
from file(i2pbase + '/apps/routerconsole/jsp/themes/console/light/images/header.png')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
<?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" />
|
||||
@ -9,6 +10,15 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- following two are for UPnP -->
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<!-- required for reliable core functionality on Android, see:
|
||||
https://geti2p.net/en/docs/applications/embedding
|
||||
heading: "Design for and Encourage Long Uptimes"
|
||||
-->
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<application
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
@ -17,6 +27,8 @@
|
||||
<service
|
||||
android:name=".service.RouterService"
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="net.i2p.android.router.service.IRouterState" />
|
||||
@ -25,17 +37,28 @@
|
||||
<provider
|
||||
android:name=".provider.CacheProvider"
|
||||
android:authorities="${applicationId}.provider" />
|
||||
<receiver android:name=".receiver.OnBootReceiver">
|
||||
<receiver
|
||||
android:name=".receiver.OnBootReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.RemoteStartReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="net.i2p.android.router.receiver.START_I2P" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name="net.i2p.android.I2PActivity"
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop">
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
<!-- Console filters -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@ -45,6 +68,10 @@
|
||||
<action android:name="net.i2p.android.router.START_I2P" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="net.i2p.android.router.service.APPROVE_SAM" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Addressbook filters -->
|
||||
<intent-filter>
|
||||
@ -95,6 +122,7 @@
|
||||
<activity
|
||||
android:name=".web.WebActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:label="I2P Web Browser">
|
||||
<!-- Disabled, this browser is not very secure
|
||||
Temporarily enabled until an alternative browser is ready -->
|
||||
|
@ -31,6 +31,13 @@ public class CustomViewPager extends ViewPager {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,8 @@ public class MaterialSeekBarController implements SeekBar.OnSeekBarChangeListene
|
||||
}
|
||||
|
||||
private void setPaddedValue(int value) {
|
||||
mSeekBarValue.setText(String.format("%0" + mMaxDigits +"d", value));
|
||||
//mSeekBarValue.setText(String.format("%0" + mMaxDigits +"d", value));
|
||||
mSeekBarValue.setText(String.format("%" + mMaxDigits +"d", value));
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ import net.i2p.android.router.MainFragment;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
import net.i2p.android.router.addressbook.AddressbookContainer;
|
||||
import net.i2p.android.router.service.AndroidSAMSecureSession;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.service.State;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
@ -134,6 +135,7 @@ public class I2PActivity extends I2PActivityBase implements
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
handleIntents();
|
||||
}
|
||||
|
||||
@ -143,17 +145,29 @@ public class I2PActivity extends I2PActivityBase implements
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
Util.d("handleIntent: intent=" + intent.toString());
|
||||
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
Util.d("handleIntent: action=" + action);
|
||||
|
||||
Bundle extra = intent.getExtras();
|
||||
if (extra != null)
|
||||
Util.d("handleIntent extra=" + extra.toString());
|
||||
|
||||
switch (action) {
|
||||
case "net.i2p.android.router.START_I2P":
|
||||
if (mViewPager.getCurrentItem() != 0)
|
||||
mViewPager.setCurrentItem(0, false);
|
||||
autoStart();
|
||||
break;
|
||||
|
||||
case "net.i2p.android.router.service.APPROVE_SAM":
|
||||
Util.w("Affirmed SAM Connection");
|
||||
String ID = extra.getString("ID");
|
||||
Util.d("SAM ID was: " + ID);
|
||||
AndroidSAMSecureSession.affirmResult(ID);
|
||||
break;
|
||||
case Intent.ACTION_PICK:
|
||||
mViewPager.setFixedPage(2, R.string.select_an_address);
|
||||
break;
|
||||
|
@ -35,6 +35,7 @@ class InitActivities {
|
||||
public InitActivities(Context c) {
|
||||
ctx = c;
|
||||
myDir = Util.getFileDir(c);
|
||||
Util.i("My app directory is "+myDir);
|
||||
_ourVersion = Util.getOurVersion(c);
|
||||
}
|
||||
|
||||
@ -59,90 +60,89 @@ class InitActivities {
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
Util.i("Initializing the I2P resources");
|
||||
|
||||
if (checkNewVersion()) {
|
||||
List<Properties> lProps = Util.getPropertiesFromPreferences(ctx);
|
||||
Properties props = lProps.get(0);
|
||||
List<Properties> lProps = Util.getPropertiesFromPreferences(ctx);
|
||||
Properties props = lProps.get(0);
|
||||
|
||||
props.setProperty("i2p.dir.temp", myDir + "/tmp");
|
||||
props.setProperty("i2p.dir.pid", myDir + "/tmp");
|
||||
// Time disabled in default router.config
|
||||
// But lots of time problems on Android, not all carriers support NITZ
|
||||
// and there was no NTP before 3.0. Tablets should be fine?
|
||||
// Phones in airplane mode with wifi enabled still a problem.
|
||||
// Deactivated phones in airplane mode definitely won't have correct time.
|
||||
if (Build.VERSION.SDK_INT < 11) // Honeycomb 3.0
|
||||
props.setProperty("time.disabled", "false");
|
||||
mergeResourceToFile(R.raw.router_config, "router.config", props);
|
||||
mergeResourceToFile(R.raw.logger_config, "logger.config", lProps.get(1));
|
||||
// This is not needed for now, i2ptunnel.config only contains tunnel
|
||||
// settings, which can now be configured manually. We don't want to
|
||||
// overwrite the user's tunnels.
|
||||
//mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config", null);
|
||||
copyResourceToFileIfAbsent(R.raw.i2ptunnel_config, "i2ptunnel.config");
|
||||
// FIXME this is a memory hog to merge this way
|
||||
mergeResourceToFile(R.raw.hosts_txt, "hosts.txt", null);
|
||||
mergeResourceToFile(R.raw.more_hosts_txt, "hosts.txt", null);
|
||||
copyResourceToFile(R.raw.blocklist_txt, "blocklist.txt");
|
||||
props.setProperty("i2p.dir.temp", myDir + "/tmp");
|
||||
props.setProperty("i2p.dir.pid", myDir + "/tmp");
|
||||
// Time disabled in default router.config
|
||||
// But lots of time problems on Android, not all carriers support NITZ
|
||||
// and there was no NTP before 3.0. Tablets should be fine?
|
||||
// Phones in airplane mode with wifi enabled still a problem.
|
||||
// Deactivated phones in airplane mode definitely won't have correct time.
|
||||
if (Build.VERSION.SDK_INT < 11) // Honeycomb 3.0
|
||||
props.setProperty("time.disabled", "false");
|
||||
mergeResourceToFile(R.raw.router_config, "router.config", props);
|
||||
mergeResourceToFile(R.raw.logger_config, "logger.config", lProps.get(1));
|
||||
// This is not needed for now, i2ptunnel.config only contains tunnel
|
||||
// settings, which can now be configured manually. We don't want to
|
||||
// overwrite the user's tunnels.
|
||||
//mergeResourceToFile(R.raw.i2ptunnel_config, "i2ptunnel.config", null);
|
||||
copyResourceToFileIfAbsent(R.raw.i2ptunnel_config, "i2ptunnel.config");
|
||||
// FIXME this is a memory hog to merge this way
|
||||
mergeResourceToFile(R.raw.hosts_txt, "hosts.txt", null);
|
||||
mergeResourceToFile(R.raw.more_hosts_txt, "hosts.txt", null);
|
||||
copyResourceToFile(R.raw.blocklist_txt, "blocklist.txt");
|
||||
|
||||
File abDir = new File(myDir, "addressbook");
|
||||
abDir.mkdir();
|
||||
copyResourceToFile(R.raw.subscriptions_txt, "addressbook/subscriptions.txt");
|
||||
mergeResourceToFile(R.raw.addressbook_config_txt, "addressbook/config.txt", null);
|
||||
File abDir = new File(myDir, "addressbook");
|
||||
abDir.mkdir();
|
||||
copyResourceToFileIfAbsent(R.raw.subscriptions_txt, "addressbook/subscriptions.txt");
|
||||
mergeResourceToFile(R.raw.addressbook_config_txt, "addressbook/config.txt", null);
|
||||
|
||||
File docsDir = new File(myDir, "docs");
|
||||
docsDir.mkdir();
|
||||
copyResourceToFile(R.raw.ahelper_conflict_header_ht, "docs/ahelper-conflict-header.ht");
|
||||
copyResourceToFile(R.raw.ahelper_new_header_ht, "docs/ahelper-new-header.ht");
|
||||
copyResourceToFile(R.raw.ahelper_notfound_header_ht, "docs/ahelper-notfound-header.ht");
|
||||
copyResourceToFile(R.raw.auth_header_ht, "docs/auth-header.ht");
|
||||
copyResourceToFile(R.raw.baduri_header_ht, "docs/baduri-header.ht");
|
||||
copyResourceToFile(R.raw.denied_header_ht, "docs/denied-header.ht");
|
||||
copyResourceToFile(R.raw.dnf_header_ht, "docs/dnf-header.ht");
|
||||
copyResourceToFile(R.raw.dnfb_header_ht, "docs/dnfb-header.ht");
|
||||
copyResourceToFile(R.raw.dnfh_header_ht, "docs/dnfh-header.ht");
|
||||
copyResourceToFile(R.raw.dnfp_header_ht, "docs/dnfp-header.ht");
|
||||
copyResourceToFile(R.raw.enc_header_ht, "docs/enc-header.ht");
|
||||
copyResourceToFile(R.raw.encp_header_ht, "docs/encp-header.ht");
|
||||
copyResourceToFile(R.raw.localhost_header_ht, "docs/localhost-header.ht");
|
||||
copyResourceToFile(R.raw.nols_header_ht, "docs/nols-header.ht");
|
||||
copyResourceToFile(R.raw.nolsp_header_ht, "docs/nolsp-header.ht");
|
||||
copyResourceToFile(R.raw.noproxy_header_ht, "docs/noproxy-header.ht");
|
||||
copyResourceToFile(R.raw.protocol_header_ht, "docs/protocol-header.ht");
|
||||
copyResourceToFile(R.raw.reset_header_ht, "docs/reset-header.ht");
|
||||
copyResourceToFile(R.raw.resetp_header_ht, "docs/resetp-header.ht");
|
||||
File docsDir = new File(myDir, "docs");
|
||||
docsDir.mkdir();
|
||||
/*copyResourceToFile(R.raw.ahelper_conflict_header_ht, "docs/ahelper-conflict-header.ht");
|
||||
copyResourceToFile(R.raw.ahelper_new_header_ht, "docs/ahelper-new-header.ht");
|
||||
copyResourceToFile(R.raw.ahelper_notfound_header_ht, "docs/ahelper-notfound-header.ht");
|
||||
copyResourceToFile(R.raw.auth_header_ht, "docs/auth-header.ht");
|
||||
copyResourceToFile(R.raw.baduri_header_ht, "docs/baduri-header.ht");
|
||||
copyResourceToFile(R.raw.denied_header_ht, "docs/denied-header.ht");
|
||||
copyResourceToFile(R.raw.dnf_header_ht, "docs/dnf-header.ht");
|
||||
copyResourceToFile(R.raw.dnfb_header_ht, "docs/dnfb-header.ht");
|
||||
copyResourceToFile(R.raw.dnfh_header_ht, "docs/dnfh-header.ht");
|
||||
copyResourceToFile(R.raw.dnfp_header_ht, "docs/dnfp-header.ht");
|
||||
copyResourceToFile(R.raw.enc_header_ht, "docs/enc-header.ht");
|
||||
copyResourceToFile(R.raw.encp_header_ht, "docs/encp-header.ht");
|
||||
copyResourceToFile(R.raw.localhost_header_ht, "docs/localhost-header.ht");
|
||||
copyResourceToFile(R.raw.nols_header_ht, "docs/nols-header.ht");
|
||||
copyResourceToFile(R.raw.nolsp_header_ht, "docs/nolsp-header.ht");
|
||||
copyResourceToFile(R.raw.noproxy_header_ht, "docs/noproxy-header.ht");
|
||||
copyResourceToFile(R.raw.protocol_header_ht, "docs/protocol-header.ht");
|
||||
copyResourceToFile(R.raw.reset_header_ht, "docs/reset-header.ht");
|
||||
copyResourceToFile(R.raw.resetp_header_ht, "docs/resetp-header.ht");*/
|
||||
|
||||
File cssDir = new File(docsDir, "themes/console/light");
|
||||
cssDir.mkdirs();
|
||||
//copyResourceToFile(R.raw.console_css, "docs/themes/console/light/console.css");
|
||||
//copyResourceToFile(R.raw.android_css, "docs/themes/console/light/android.css");
|
||||
File cssDir = new File(docsDir, "themes/console/light");
|
||||
cssDir.mkdirs();
|
||||
//copyResourceToFile(R.raw.console_css, "docs/themes/console/light/console.css");
|
||||
//copyResourceToFile(R.raw.android_css, "docs/themes/console/light/android.css");
|
||||
|
||||
File imgDir = new File(docsDir, "themes/console/images");
|
||||
imgDir.mkdir();
|
||||
copyResourceToFile(R.drawable.i2plogo, "docs/themes/console/images/i2plogo.png");
|
||||
copyResourceToFile(R.drawable.itoopie_sm, "docs/themes/console/images/itoopie_sm.png");
|
||||
//copyResourceToFile(R.drawable.outbound, "docs/themes/console/images/outbound.png");
|
||||
//copyResourceToFile(R.drawable.inbound, "docs/themes/console/images/inbound.png");
|
||||
File imgDir = new File(docsDir, "themes/console/images");
|
||||
imgDir.mkdir();
|
||||
copyResourceToFile(R.drawable.i2plogo, "docs/themes/console/images/i2plogo.png");
|
||||
copyResourceToFile(R.drawable.itoopie_sm, "docs/themes/console/images/itoopie_sm.png");
|
||||
//copyResourceToFile(R.drawable.outbound, "docs/themes/console/images/outbound.png");
|
||||
//copyResourceToFile(R.drawable.inbound, "docs/themes/console/images/inbound.png");
|
||||
|
||||
File img2Dir = new File(cssDir, "images");
|
||||
img2Dir.mkdir();
|
||||
//copyResourceToFile(R.drawable.header, "docs/themes/console/light/images/header.png");
|
||||
File img2Dir = new File(cssDir, "images");
|
||||
img2Dir.mkdir();
|
||||
//copyResourceToFile(R.drawable.header, "docs/themes/console/light/images/header.png");
|
||||
|
||||
File certDir = new File(myDir, "certificates");
|
||||
certDir.mkdir();
|
||||
File certificates = new File(myDir, "certificates");
|
||||
File[] allcertificates = certificates.listFiles();
|
||||
if ( allcertificates != null) {
|
||||
for (File f : allcertificates) {
|
||||
Util.d("Deleting old certificate file/dir " + f);
|
||||
FileUtil.rmdir(f, false);
|
||||
}
|
||||
File certDir = new File(myDir, "certificates");
|
||||
certDir.mkdir();
|
||||
File certificates = new File(myDir, "certificates");
|
||||
File[] allCertificates = certificates.listFiles();
|
||||
if ( allCertificates != null) {
|
||||
for (File f : allCertificates) {
|
||||
Util.d("Deleting old certificate file/dir " + f);
|
||||
FileUtil.rmdir(f, false);
|
||||
}
|
||||
unzipResourceToDir(R.raw.certificates_zip, "certificates");
|
||||
//File netDBDir = new File(myDir, "netDB");
|
||||
//netDBDir.mkdir();
|
||||
//unzipResourceToDir(R.raw.netdb_zip, "netDB");
|
||||
}
|
||||
File netDBDir = new File(myDir, "netDB");
|
||||
netDBDir.mkdir();
|
||||
//unzipResourceToDir(R.raw.netdb_zip, "netDB");
|
||||
unzipResourceToDir(R.raw.certificates_zip, "certificates");
|
||||
|
||||
// Set up the locations so settings can find them
|
||||
System.setProperty("i2p.dir.base", myDir);
|
||||
@ -178,10 +178,16 @@ class InitActivities {
|
||||
out.write(buf, 0, read);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Util.e("copyResourceToFile" + "IOE: ", ioe);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
Util.e("copyResourceToFile" + "NFE: ", nfe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {
|
||||
Util.e("copyResourceToFile" + "IOE in.close(): ", ioe);
|
||||
}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {
|
||||
Util.e("copyResourceToFile" + "IOE out.close(): ", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -192,7 +198,7 @@ class InitActivities {
|
||||
FileOutputStream out = null;
|
||||
ZipInputStream zis = null;
|
||||
|
||||
Util.d("Creating files in '" + myDir + "/" + folder + "/' from resource");
|
||||
Util.i("Creating files in '" + myDir + "/" + folder + "/' from resource");
|
||||
try {
|
||||
// Context methods
|
||||
in = ctx.getResources().openRawResource(resID);
|
||||
@ -200,6 +206,7 @@ class InitActivities {
|
||||
ZipEntry ze;
|
||||
while ((ze = zis.getNextEntry()) != null) {
|
||||
out = null;
|
||||
Util.i("unzipping "+ze);
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
@ -209,26 +216,51 @@ class InitActivities {
|
||||
}
|
||||
String name = ze.getName();
|
||||
File f = new File(myDir + "/" + folder +"/" + name);
|
||||
if (ze.isDirectory()) {
|
||||
Util.d("Creating directory " + myDir + "/" + folder +"/" + name + " from resource");
|
||||
String canonicalPath = f.getCanonicalPath().replace("/user/0/", "/data/");
|
||||
// account for canonical path differences when using .aab bundles
|
||||
if (!canonicalPath.startsWith(myDir.replace("/user/0/", "/data/"))) {
|
||||
// If these don't match, there's a path-traversal possibility.
|
||||
// So ignore it.
|
||||
Util.e("Path mismatch bug " + canonicalPath.toString() + " " + myDir.toString());
|
||||
} else if (ze.isDirectory()) {
|
||||
Util.i("Creating directory " + myDir + "/" + folder +"/" + name + " from resource");
|
||||
f.mkdir();
|
||||
} else {
|
||||
Util.d("Creating file " + myDir + "/" + folder +"/" + name + " from resource");
|
||||
Util.i("Creating file " + myDir + "/" + folder +"/" + name + " from resource");
|
||||
//create all the leading directories
|
||||
File newFile = new File(myDir+"/"+folder+"/"+name);
|
||||
newFile.getParentFile().mkdirs();
|
||||
byte[] bytes = baos.toByteArray();
|
||||
out = new FileOutputStream(f);
|
||||
out.write(bytes);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Util.e("unzipResourceToDir" + "IOE: ", ioe);
|
||||
} finally {
|
||||
if (out != null) { try { out.close(); } catch (IOException ioe) {} out = null; }
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
Util.e("unzipResourceToDir" + "IOE: interior out.close ", ioe);
|
||||
}
|
||||
out = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Util.e("unzipResourceToDir" + "IOE: ", ioe);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
Util.e("unzipResourceToDir" + "NFE: ", nfe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
if (zis != null) try { zis.close(); } catch (IOException ioe) {}
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {
|
||||
Util.e("unzipResourceToDir" + "IOE: in.close() ", ioe);
|
||||
}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {
|
||||
Util.e("unzipResourceToDir" + "IOE: out.close() ", ioe);
|
||||
}
|
||||
if (zis != null) try { zis.close(); } catch (IOException ioe) {
|
||||
Util.e("unzipResourceToDir" + "IOE: zis.close() ", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,18 +284,31 @@ class InitActivities {
|
||||
private boolean checkNewVersion() {
|
||||
Properties props = new Properties();
|
||||
|
||||
Util.i("Checking for a new install/version");
|
||||
InputStream fin = null;
|
||||
try {
|
||||
fin = ctx.openFileInput(CONFIG_FILE);
|
||||
DataHelper.loadProps(props, fin);
|
||||
} catch (IOException ioe) {
|
||||
Util.d("Looks like a new install");
|
||||
Util.i("Looks like a new install");
|
||||
} finally {
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
if (fin != null) {
|
||||
try {
|
||||
Util.i("fin was not null "+CONFIG_FILE);
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
Util.i("Error loading config:", ioe);
|
||||
}
|
||||
}else {
|
||||
Util.i("fin was null");
|
||||
}
|
||||
}
|
||||
|
||||
String oldVersion = props.getProperty(PROP_INSTALLED_VERSION);
|
||||
Util.i("Old version is:"+oldVersion);
|
||||
boolean newInstall = oldVersion == null;
|
||||
if (newInstall)
|
||||
return true;
|
||||
boolean newVersion = !_ourVersion.equals(oldVersion);
|
||||
|
||||
if (newVersion) {
|
||||
|
@ -5,6 +5,8 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EepGet;
|
||||
@ -83,7 +85,7 @@ public class EepGetFetcher implements EepGet.StatusListener {
|
||||
int semi = rv.indexOf(";");
|
||||
if (semi > 0)
|
||||
rv = rv.substring(0, semi);
|
||||
return rv.toLowerCase();
|
||||
return rv.toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,21 +1,40 @@
|
||||
package net.i2p.android.apps;
|
||||
|
||||
import static net.i2p.app.ClientAppState.INITIALIZED;
|
||||
import static net.i2p.app.ClientAppState.RUNNING;
|
||||
import static net.i2p.app.ClientAppState.STARTING;
|
||||
import static net.i2p.app.ClientAppState.STOPPED;
|
||||
import static net.i2p.app.ClientAppState.STOPPING;
|
||||
import static net.i2p.app.ClientAppState.UNINITIALIZED;
|
||||
import static net.i2p.update.UpdateType.BLOCKLIST;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import net.i2p.android.router.NewsActivity;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import net.i2p.crypto.SU3File;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.Banlist;
|
||||
import net.i2p.router.Blocklist;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.news.BlocklistEntries;
|
||||
import net.i2p.router.news.NewsEntry;
|
||||
import net.i2p.router.news.NewsMetadata;
|
||||
import net.i2p.router.news.NewsXMLParser;
|
||||
import net.i2p.router.util.RFC822Date;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RFC822Date;
|
||||
import net.i2p.util.ReusableGZIPInputStream;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
@ -27,13 +46,17 @@ import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* From router console, simplified since we don't deal with router versions
|
||||
* or updates.
|
||||
*
|
||||
* As of 0.9.41, implements ClientApp to hang us off the ClientAppManager,
|
||||
* so we can remove the static reference.
|
||||
*/
|
||||
public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
public class NewsFetcher implements Runnable, EepGet.StatusListener, ClientApp {
|
||||
private final Context mCtx;
|
||||
private final RouterContext _context;
|
||||
private final Notifications _notif;
|
||||
@ -44,20 +67,22 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private boolean _invalidated;
|
||||
private File _newsFile;
|
||||
private File _tempFile;
|
||||
private static NewsFetcher _instance;
|
||||
private volatile boolean _isRunning = true;
|
||||
private Thread _thread;
|
||||
private final ClientAppManager _mgr;
|
||||
private volatile ClientAppState _state = UNINITIALIZED;
|
||||
public static final String APP_NAME = "NewsFetcher";
|
||||
|
||||
public static /*final */ NewsFetcher getInstance() {
|
||||
return _instance;
|
||||
}
|
||||
static final String PROP_BLOCKLIST_TIME = "router.blocklistVersion";
|
||||
private static final String BLOCKLIST_DIR = "docs/feed/blocklist";
|
||||
private static final String BLOCKLIST_FILE = "blocklist.txt";
|
||||
|
||||
/**
|
||||
* As of 0.9.41, returns a new one every time. Only call once.
|
||||
*/
|
||||
public static /* final */ synchronized NewsFetcher getInstance(
|
||||
Context context, RouterContext ctx, Notifications notif) {
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
_instance = new NewsFetcher(context, ctx, notif);
|
||||
return _instance;
|
||||
return new NewsFetcher(context, ctx, notif);
|
||||
}
|
||||
|
||||
private static final String NEWS_DIR = "docs";
|
||||
@ -71,18 +96,17 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
*
|
||||
* @since 0.7.14 not configurable
|
||||
*/
|
||||
private static final String BACKUP_NEWS_URL_SU3 = "http://avviiexdngd32ccoy4kuckvc3mkf53ycvzbz6vz75vzhv4tbpk5a.b32.i2p/news.su3";
|
||||
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 PROP_NEWS_URL = "router.newsURL";
|
||||
public static final String DEFAULT_NEWS_URL_SU3 = "http://echelon.i2p/news/news.su3";
|
||||
public static final String DEFAULT_NEWS_URL_SU3 = "http://tc73n4kivdroccekirco7rhgxdg5f3cjvbaapabupeyzrqwv5guq.b32.i2p/news.su3";
|
||||
|
||||
private NewsFetcher(Context context, RouterContext ctx, Notifications notif) {
|
||||
mCtx = context;
|
||||
_context = ctx;
|
||||
_notif = notif;
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
_log = ctx.logManager().getLog(NewsFetcher.class);
|
||||
try {
|
||||
String last = ctx.getProperty(PROP_LAST_CHECKED);
|
||||
@ -96,6 +120,9 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
_newsFile = new File(newsDir, NEWS_FILE);
|
||||
_tempFile = new File(_context.getTempDir(), TEMP_NEWS_FILE);
|
||||
updateLastFetched();
|
||||
_mgr = ctx.clientAppManager();
|
||||
changeState(INITIALIZED);
|
||||
_mgr.register(this);
|
||||
}
|
||||
|
||||
private void updateLastFetched() {
|
||||
@ -134,7 +161,17 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private static final long RUN_DELAY = 30 * 60 * 1000;
|
||||
|
||||
public void run() {
|
||||
_thread = Thread.currentThread();
|
||||
changeState(RUNNING);
|
||||
try {
|
||||
run2();
|
||||
} finally {
|
||||
_mgr.unregister(this);
|
||||
changeState(STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void run2() {
|
||||
try {
|
||||
Thread.sleep(INITIAL_DELAY);
|
||||
} catch (InterruptedException ie) {
|
||||
@ -276,14 +313,6 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
public void attempting(String url) {
|
||||
}
|
||||
|
||||
private class Shutdown implements Runnable {
|
||||
public void run() {
|
||||
_isRunning = false;
|
||||
if (_thread != null)
|
||||
_thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SU3 handlers
|
||||
//
|
||||
@ -323,6 +352,11 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
xml.delete();
|
||||
NewsMetadata data = parser.getMetadata();
|
||||
List<NewsEntry> entries = parser.getEntries();
|
||||
BlocklistEntries ble = parser.getBlocklistEntries();
|
||||
if (ble != null && ble.isVerified())
|
||||
processBlocklistEntries(ble);
|
||||
else
|
||||
_log.info("No blocklist entries found in news feed");
|
||||
String sudVersion = su3.getVersionString();
|
||||
String signingKeyName = su3.getSignerString();
|
||||
File to3 = new File(_context.getTempDir(), "tmp3-" + _context.random().nextInt() + ".xml");
|
||||
@ -333,6 +367,104 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process blocklist entries
|
||||
*
|
||||
* @since 0.9.57
|
||||
*/
|
||||
private void processBlocklistEntries(BlocklistEntries ble) {
|
||||
long oldTime = _context.getProperty(PROP_BLOCKLIST_TIME, 0L);
|
||||
if (ble.updated <= oldTime) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Not processing blocklist " + DataHelper.formatDate(ble.updated) +
|
||||
", already have " + DataHelper.formatDate(oldTime));
|
||||
return;
|
||||
}
|
||||
Blocklist bl = _context.blocklist();
|
||||
Banlist ban = _context.banlist();
|
||||
String reason = "Blocklist feed " + DataHelper.formatDate(ble.updated);
|
||||
int banned = 0;
|
||||
for (Iterator<String> iter = ble.entries.iterator(); iter.hasNext(); ) {
|
||||
String s = iter.next();
|
||||
if (s.length() == 44) {
|
||||
byte[] b = Base64.decode(s);
|
||||
if (b == null || b.length != Hash.HASH_LENGTH) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
Hash h = Hash.create(b);
|
||||
if (!ban.isBanlistedForever(h)) {
|
||||
ban.banlistRouterForever(h, reason);
|
||||
_context.commSystem().forceDisconnect(h);
|
||||
}
|
||||
} else {
|
||||
byte[] ip = Addresses.getIP(s);
|
||||
if (ip == null) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
if (!bl.isBlocklisted(ip))
|
||||
bl.add(ip);
|
||||
}
|
||||
if (++banned >= BlocklistEntries.MAX_ENTRIES) {
|
||||
// prevent somebody from destroying the whole network
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (String s : ble.removes) {
|
||||
if (s.length() == 44) {
|
||||
byte[] b = Base64.decode(s);
|
||||
if (b == null || b.length != Hash.HASH_LENGTH)
|
||||
continue;
|
||||
Hash h = Hash.create(b);
|
||||
if (ban.isBanlistedForever(h))
|
||||
ban.unbanlistRouter(h);
|
||||
} else {
|
||||
byte[] ip = Addresses.getIP(s);
|
||||
if (ip == null)
|
||||
continue;
|
||||
if (bl.isBlocklisted(ip))
|
||||
bl.remove(ip);
|
||||
}
|
||||
}
|
||||
// Save the blocks. We do not save the unblocks.
|
||||
File f = new SecureFile(_context.getConfigDir(), BLOCKLIST_DIR);
|
||||
f.mkdirs();
|
||||
f = new File(f, BLOCKLIST_FILE);
|
||||
boolean fail = false;
|
||||
BufferedWriter out = null;
|
||||
try {
|
||||
out = new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(f), "UTF-8"));
|
||||
out.write("# ");
|
||||
out.write(ble.supdated);
|
||||
out.newLine();
|
||||
banned = 0;
|
||||
for (String s : ble.entries) {
|
||||
s = s.replace(':', ';'); // IPv6
|
||||
out.write(reason);
|
||||
out.write(':');
|
||||
out.write(s);
|
||||
out.newLine();
|
||||
if (++banned >= BlocklistEntries.MAX_ENTRIES)
|
||||
break;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing blocklist", ioe);
|
||||
fail = true;
|
||||
} finally {
|
||||
if (out != null) try {
|
||||
out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
if (!fail) {
|
||||
f.setLastModified(ble.updated);
|
||||
String upd = Long.toString(ble.updated);
|
||||
_context.router().saveConfig(PROP_BLOCKLIST_TIME, upd);
|
||||
}
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Processed " + ble.entries.size() + " blocks and " + ble.removes.size() + " unblocks from news feed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gunzip the file
|
||||
*
|
||||
@ -417,4 +549,69 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
////// begin ClientApp interface
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
public synchronized void startup() {
|
||||
changeState(STARTING);
|
||||
_thread = new I2PAppThread(this, "NewsFetcher", true);
|
||||
_thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
public synchronized void shutdown(String[] args) {
|
||||
if (_state != RUNNING)
|
||||
return;
|
||||
changeState(STOPPING);
|
||||
_isRunning = false;
|
||||
if (_thread != null)
|
||||
_thread.interrupt();
|
||||
changeState(STOPPED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
public ClientAppState getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
public String getName() {
|
||||
return APP_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return APP_NAME;
|
||||
}
|
||||
|
||||
////// end ClientApp interface
|
||||
////// begin ClientApp helpers
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
private void changeState(ClientAppState state) {
|
||||
changeState(state, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.41
|
||||
*/
|
||||
private synchronized void changeState(ClientAppState state, Exception e) {
|
||||
_state = state;
|
||||
_mgr.notify(this, state, null, e);
|
||||
}
|
||||
|
||||
////// end ClientApp helpers
|
||||
}
|
||||
|
@ -1,19 +1,26 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class Browser implements Comparable<Browser> {
|
||||
public final String packageName;
|
||||
public final CharSequence label;
|
||||
public final Drawable icon;
|
||||
public final boolean isInstalled;
|
||||
public final boolean isKnown;
|
||||
public final boolean isSupported;
|
||||
public final boolean isRecommended;
|
||||
|
||||
private boolean isInstalled;
|
||||
/**
|
||||
* A browser that we don't know about.
|
||||
*
|
||||
@ -80,4 +87,23 @@ public class Browser implements Comparable<Browser> {
|
||||
} else
|
||||
return 2;
|
||||
}
|
||||
|
||||
public boolean isInstalled(Context context){
|
||||
if (isInstalled) {
|
||||
return true;
|
||||
}
|
||||
// Find all installed browsers that listen for ".i2p"
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("http://stats.i2p"));
|
||||
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
|
||||
for (ResolveInfo browser : installedBrowsers) {
|
||||
if (browser.activityInfo.packageName.equals(packageName)) {
|
||||
isInstalled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isInstalled;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -71,11 +73,15 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
||||
holder.mLabel.setText(browser.label);
|
||||
|
||||
if (browser.isKnown) {
|
||||
if (browser.isRecommended && browser.isInstalled) {
|
||||
if (browser.isRecommended && browser.isInstalled(mCtx)) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (browser.isSupported && !browser.isInstalled) {
|
||||
} else if (browser.isSupported && browser.isInstalled(mCtx)) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
||||
holder.mStatus.setVisibility(View.INVISIBLE);
|
||||
} else if (browser.isSupported && !browser.isInstalled(mCtx)) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
|
||||
holder.mStatus.setOnClickListener(new View.OnClickListener() {
|
||||
@ -84,11 +90,15 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
||||
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
||||
Uri uri = Uri.parse(uriMarket);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
mCtx.startActivity(intent);
|
||||
try {
|
||||
mCtx.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(mCtx, R.string.no_market_app, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (!browser.isSupported) {
|
||||
} else if (browser.isInstalled(mCtx) && !browser.isSupported) {
|
||||
// Make the icon gray-scale to show it is unsupported
|
||||
ColorMatrix matrix = new ColorMatrix();
|
||||
matrix.setSaturation(0);
|
||||
|
@ -26,6 +26,8 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -100,8 +102,28 @@ public class BrowserListFragment extends Fragment implements
|
||||
getContext().getResources().getStringArray(R.array.supported_browsers));
|
||||
supportedLabels = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.supported_browser_labels));
|
||||
unsupported = Arrays.asList(
|
||||
context.getResources().getStringArray(R.array.unsupported_browsers));
|
||||
unsupported = allBrowsers(context);//Arrays.asList(
|
||||
//context.getResources().getStringArray(R.array.unsupported_browsers));
|
||||
}
|
||||
|
||||
public List<String> allBrowsers(Context context){
|
||||
//try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("http://www.google.com"));
|
||||
List<ResolveInfo> browserList;
|
||||
PackageManager pm = context.getPackageManager();
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
// gets all
|
||||
browserList = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
|
||||
} else {
|
||||
browserList = pm.queryIntentActivities(intent, 0);
|
||||
}
|
||||
//}catch()
|
||||
List<String> finalResult = new ArrayList<String>();
|
||||
for (ResolveInfo ri : browserList){
|
||||
finalResult.add(ri.resolvePackageName);
|
||||
}
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,8 +143,9 @@ public class BrowserListFragment extends Fragment implements
|
||||
intent.setData(Uri.parse("http://stats.i2p"));
|
||||
|
||||
final PackageManager pm = getContext().getPackageManager();
|
||||
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
|
||||
Set<ResolveInfo> installedBrowsers = new HashSet<>(pm.queryIntentActivities(intent, 0));
|
||||
|
||||
// Compare installed browsers to supported browsers
|
||||
for (ResolveInfo browser : installedBrowsers) {
|
||||
if (recommended.contains(browser.activityInfo.packageName)) {
|
||||
browsers.add(new Browser(pm, browser, true, true));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -22,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;
|
||||
@ -41,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();
|
||||
@ -73,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 == null ? null : mGroup.getControllers();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
mGroup = null;
|
||||
controllers = null;
|
||||
error = iae.toString();
|
||||
}
|
||||
|
||||
if (mGroup == null) {
|
||||
// Show error
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
error, Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
||||
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
||||
mTunnel = new TunnelEntry(getActivity(),
|
||||
mGroup.getControllers().get(tunnelId),
|
||||
tunnelId);
|
||||
try {
|
||||
TunnelController controller = controllers.get(tunnelId);
|
||||
mTunnel = new TunnelEntry(getActivity(), controller, tunnelId);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// Tunnel doesn't exist
|
||||
Util.e("Could not load tunnel details", e);
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,18 +123,18 @@ 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());
|
||||
ViewCompat.setTransitionName(name,
|
||||
getActivity().getString(R.string.TUNNEL_NAME) + mTunnel.getId());
|
||||
|
||||
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
||||
type.setText(mTunnel.getType());
|
||||
|
||||
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
||||
description.setText(mTunnel.getDescription());
|
||||
ViewCompat.setTransitionName(description,
|
||||
getActivity().getString(R.string.TUNNEL_DESCRIPTION) + mTunnel.getId());
|
||||
|
||||
if (!mTunnel.getDetails().isEmpty()) {
|
||||
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
|
||||
@ -204,7 +220,13 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -244,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;
|
||||
@ -257,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();
|
||||
@ -265,42 +297,31 @@ 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());
|
||||
@ -313,4 +334,47 @@ public class TunnelDetailFragment extends Fragment {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,16 +6,24 @@ 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;
|
||||
|
||||
/**
|
||||
* A single tunnel.
|
||||
* Stored by the TunnelEntryAdapter.
|
||||
*/
|
||||
public class TunnelEntry {
|
||||
public static final int RUNNING = 1;
|
||||
public static final int STARTING = 2;
|
||||
@ -26,18 +34,33 @@ public class TunnelEntry {
|
||||
private final TunnelController mController;
|
||||
private final int mId;
|
||||
|
||||
/**
|
||||
* @param tcg non-null
|
||||
* @return the new TunnelEntry, or null if there was an error.
|
||||
*/
|
||||
public static TunnelEntry createNewTunnel(
|
||||
Context ctx,
|
||||
TunnelControllerGroup tcg,
|
||||
TunnelConfig cfg) {
|
||||
int tunnelId = tcg.getControllers().size();
|
||||
List<String> msgs = TunnelUtil.saveTunnel(
|
||||
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) {
|
||||
@ -262,6 +285,8 @@ public class TunnelEntry {
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
||||
case STARTING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.ic_sync_black_24dp);
|
||||
case RUNNING:
|
||||
case NOT_RUNNING:
|
||||
default:
|
||||
|
@ -5,22 +5,31 @@ import android.os.Build;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.util.FragmentUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains the List of TunnelEntries.
|
||||
* There's two of these, one for client tunnels and
|
||||
* one for server tunnels.
|
||||
* Created by the TunnelListFragment.
|
||||
*/
|
||||
public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private 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.
|
||||
@ -34,10 +43,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);
|
||||
@ -65,6 +74,8 @@ 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);
|
||||
if (wasEmpty) {
|
||||
@ -120,13 +131,24 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
||||
}
|
||||
}
|
||||
|
||||
private void setClipboard(Context context, String text) {
|
||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText("Copied Text", text);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||
switch (holder.getItemViewType()) {
|
||||
case R.string.router_not_running:
|
||||
((TextView) holder.itemView).setText(
|
||||
mCtx.getString(R.string.router_not_running));
|
||||
mCtx.getString(R.string.i2ptunnel_not_initialized));
|
||||
break;
|
||||
|
||||
case R.layout.listitem_empty:
|
||||
@ -144,32 +166,49 @@ 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());
|
||||
tvh.interfacePort.setText(tunnel.getTunnelLink(false));
|
||||
|
||||
ViewCompat.setTransitionName(tvh.name,
|
||||
mCtx.getString(R.string.TUNNEL_NAME) + tunnel.getId());
|
||||
ViewCompat.setTransitionName(tvh.description,
|
||||
mCtx.getString(R.string.TUNNEL_DESCRIPTION) + tunnel.getId());
|
||||
|
||||
tvh.itemView.setSelected(mTwoPane.isTwoPane() && position == mActivatedPosition);
|
||||
tvh.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
// TODO
|
||||
// lint priority 8/10
|
||||
// lint: Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later
|
||||
// javadocs: Note that unlike ListView, RecyclerView will not call this method again
|
||||
// if the position of the item changes in the data set unless the item itself is invalidated
|
||||
// or the new position cannot be determined.
|
||||
// For this reason, you should only use the position parameter while acquiring
|
||||
// the related data item inside this method and should not keep a copy of it.
|
||||
// If you need the position of an item later on (e.g. in a click listener),
|
||||
// use RecyclerView.ViewHolder.getAdapterPosition() which will have the updated adapter position.
|
||||
int oldPosition = mActivatedPosition;
|
||||
mActivatedPosition = position;
|
||||
notifyItemChanged(oldPosition);
|
||||
notifyItemChanged(position);
|
||||
Pair<View, String> namePair = Pair.create(
|
||||
(View)tvh.name,
|
||||
ViewCompat.getTransitionName(tvh.name));
|
||||
Pair<View, String> descPair = Pair.create(
|
||||
(View)tvh.description,
|
||||
ViewCompat.getTransitionName(tvh.description));
|
||||
Pair<View, String>[] pairs = new Pair[]{ namePair, descPair};
|
||||
Pair<View, String> statusPair = Pair.create(
|
||||
(View)tvh.status,
|
||||
ViewCompat.getTransitionName(tvh.status));
|
||||
Pair<View, String>[] pairs = new Pair[]{ statusPair};
|
||||
mListener.onTunnelSelected(tunnel.getId(), pairs);
|
||||
view.invalidate();
|
||||
}
|
||||
});
|
||||
tvh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
//@Override
|
||||
public boolean onLongClick(View view) {
|
||||
setClipboard(mCtx, tunnel.getDestHashBase32());
|
||||
Toast clipboardMessage = Toast.makeText(mCtx, R.string.copied_base32_system_notification_title, Toast. LENGTH_LONG);
|
||||
clipboardMessage.setGravity(Gravity.TOP, 0, 0); //optional
|
||||
clipboardMessage.show();
|
||||
view.invalidate();
|
||||
return true;
|
||||
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -177,6 +216,7 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Return the size of the dataset (invoked by the layout manager)
|
||||
|
@ -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) {
|
||||
|
@ -31,6 +31,12 @@ import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The list of tunnels.
|
||||
* There's two of these, one for client tunnels and
|
||||
* one for server tunnels.
|
||||
* Creates the TunnelEntryAdapter.
|
||||
*/
|
||||
public class TunnelListFragment extends Fragment implements
|
||||
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
|
||||
public static final String SHOW_CLIENT_TUNNELS = "show_client_tunnels";
|
||||
@ -150,14 +156,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() {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,11 @@ import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The top level Fragment of the tunnels tabs.
|
||||
* Creates client and server TunnelListFragments,
|
||||
* the options menu, and the new tunnel wizard button.
|
||||
*/
|
||||
public class TunnelsContainer extends Fragment implements
|
||||
FragmentUtils.TwoPaneProvider,
|
||||
TunnelListFragment.OnTunnelSelectedListener,
|
||||
@ -65,6 +70,13 @@ public class TunnelsContainer extends Fragment implements
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
private static boolean showActions() {
|
||||
RouterContext rCtx = Util.getRouterContext();
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
return rCtx != null && tcg != null &&
|
||||
tcg.getState() == ClientAppState.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.container_tunnels, container, false);
|
||||
@ -72,6 +84,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 +167,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 +195,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 +215,26 @@ 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();
|
||||
if (tcg == null) {
|
||||
// router went away
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
R.string.router_not_running, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
|
||||
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
|
||||
|
||||
if (tunnel.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,21 @@ import android.os.Bundle;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.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;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
|
||||
protected static final String ARG_TUNNEL_ID = "tunnelId";
|
||||
|
||||
@ -31,13 +38,30 @@ public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragm
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +86,21 @@ public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragm
|
||||
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(2, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Util.e("Interrupted while saving tunnel config", e);
|
||||
} catch (ExecutionException e) {
|
||||
Util.e("Error while saving tunnel config", e);
|
||||
} catch (CancellationException e) {
|
||||
Util.e("Cancelled while saving tunnel config", e);
|
||||
} catch (TimeoutException e) {
|
||||
Util.e("Timed out while savomg tunnel config", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.i2p.android.i2ptunnel.preferences;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,14 +137,17 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Set<String> interfaceSet = Addresses.getAllAddresses();
|
||||
final String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
reachableBy.setEntries(interfaces);
|
||||
reachableBy.setEntryValues(interfaces);
|
||||
reachableBy.setEnabled(true);
|
||||
}
|
||||
});
|
||||
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();
|
||||
@ -164,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
|
||||
@ -181,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)));
|
||||
|
||||
@ -197,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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -7,6 +7,7 @@ import android.os.Bundle;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.android.wizard.model.Page;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
@ -99,6 +100,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 +124,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 +184,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 +216,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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,6 +330,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 +351,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 +420,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 +452,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 +569,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();
|
||||
@ -574,21 +590,26 @@ public class TunnelUtil extends GeneralHelper {
|
||||
// Update the TunnelConfig from the tunnel wizard settings
|
||||
String kClientServer = res.getString(R.string.i2ptunnel_wizard_k_client_server);
|
||||
String kType = res.getString(R.string.i2ptunnel_wizard_k_type);
|
||||
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
|
||||
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
|
||||
String type = getTypeFromName(typeName, ctx);
|
||||
cfg.setType(type);
|
||||
try {
|
||||
String clientServer = data.getBundle(kClientServer).getString(Page.SIMPLE_DATA_KEY);
|
||||
String typeName = data.getBundle(clientServer + ":" + kType).getString(Page.SIMPLE_DATA_KEY);
|
||||
String type = getTypeFromName(typeName, ctx);
|
||||
cfg.setType(type);
|
||||
|
||||
new TunnelConfigFromWizard(cfg, data, res, _group, type).runLogic();
|
||||
} catch (NullPointerException ex) {
|
||||
Util.e("Exception while trying to create config from wizard: "+ex.getMessage());
|
||||
}
|
||||
|
||||
new TunnelConfigFromWizard(cfg, data, res, _group, type).runLogic();
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -3,12 +3,11 @@ package net.i2p.android.preferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
|
||||
public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
|
||||
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";
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.i2p.android.preferences;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
@ -30,6 +31,7 @@ public class TransportsPreferenceFragment extends I2PreferenceFragment {
|
||||
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_label_transports);
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private void loadProperties() {
|
||||
Context context= getActivity();
|
||||
RouterContext ctx = Util.getRouterContext();
|
||||
|
@ -68,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ public class IntListPreference extends ListPreference {
|
||||
getPersistedInt(0);
|
||||
} catch (ClassCastException e) {
|
||||
// Fix for where this preference was previously stored in a ListPreference
|
||||
getSharedPreferences().edit().remove(getKey()).commit();
|
||||
getSharedPreferences().edit().remove(getKey()).apply();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
|
||||
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu.OnFloatingActionsMenuUpdateListener;
|
||||
import net.i2p.android.router.dialog.AboutDialog;
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
import net.i2p.android.router.log.LogActivity;
|
||||
@ -42,6 +43,17 @@ public class ConsoleContainer extends Fragment {
|
||||
}
|
||||
|
||||
mConsoleMenu = (FloatingActionsMenu) v.findViewById(R.id.console_action_menu);
|
||||
// update visibility based on router state
|
||||
mConsoleMenu.setOnFloatingActionsMenuUpdateListener(new OnFloatingActionsMenuUpdateListener() {
|
||||
public void onMenuExpanded() {
|
||||
// this is called after the animation starts, sadly
|
||||
setMenuVisibility();
|
||||
}
|
||||
public void onMenuCollapsed() {
|
||||
// call it here too so the expand animation isn't glitchy as often
|
||||
setMenuVisibility();
|
||||
}
|
||||
});
|
||||
mConsoleMenu.findViewById(R.id.action_news).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
@ -63,13 +75,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,11 +99,6 @@ public class ConsoleContainer extends Fragment {
|
||||
inflater.inflate(R.menu.activity_main_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
setMenuVisibility();
|
||||
}
|
||||
|
||||
private void setMenuVisibility() {
|
||||
boolean routerRunning = Util.getRouterContext() != null;
|
||||
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
||||
@ -100,8 +107,8 @@ public class ConsoleContainer extends Fragment {
|
||||
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_peers).setVisibility(
|
||||
// advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
|
||||
advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -23,6 +28,7 @@ import android.widget.ScrollView;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import net.i2p.android.I2PActivityBase;
|
||||
@ -69,6 +75,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
private TextView vAdvStatusText;
|
||||
|
||||
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
|
||||
private static final String PREF_CONFIGURE_BATTERY = "app.dialog.configureBattery";
|
||||
private static final String PREF_FIRST_START = "app.router.firstStart";
|
||||
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
|
||||
protected static final String PROP_NEW_INSTALL = "i2p.newInstall";
|
||||
@ -320,7 +327,10 @@ public class MainFragment extends I2PFragmentBase {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateState(State newState) {
|
||||
/**
|
||||
* Changes the logo based on the state.
|
||||
*/
|
||||
private void updateState(State newState) {
|
||||
if (newState == State.INIT ||
|
||||
newState == State.STOPPED ||
|
||||
newState == State.MANUAL_STOPPED ||
|
||||
@ -328,13 +338,13 @@ public class MainFragment extends I2PFragmentBase {
|
||||
newState == State.NETWORK_STOPPED) {
|
||||
mConsoleLights.setImageResource(R.drawable.routerlogo_0);
|
||||
} else if (newState == State.STARTING ||
|
||||
//newState == State.GRACEFUL_SHUTDOWN || // Don't change lights for graceful
|
||||
newState == State.STOPPING ||
|
||||
newState == State.MANUAL_STOPPING ||
|
||||
newState == State.MANUAL_QUITTING ||
|
||||
newState == State.NETWORK_STOPPING) {
|
||||
mConsoleLights.setImageResource(R.drawable.routerlogo_1);
|
||||
} else if (newState == State.RUNNING) {
|
||||
} else if (newState == State.RUNNING ||
|
||||
newState == State.GRACEFUL_SHUTDOWN) {
|
||||
mConsoleLights.setImageResource(R.drawable.routerlogo_2);
|
||||
} else if (newState == State.ACTIVE) {
|
||||
mConsoleLights.setImageResource(R.drawable.routerlogo_3);
|
||||
@ -375,9 +385,9 @@ public class MainFragment extends I2PFragmentBase {
|
||||
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||
int active = ctx.commSystem().countActivePeers();
|
||||
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||
vUptime.setText("" + uptime);
|
||||
vActive.setText("" + active);
|
||||
vKnown.setText("" + known);
|
||||
vUptime.setText(uptime);
|
||||
vActive.setText(Integer.toString(active));
|
||||
vKnown.setText(Integer.toString(known));
|
||||
|
||||
// Load running tunnels
|
||||
loadDestinations(ctx);
|
||||
@ -396,20 +406,21 @@ public class MainFragment extends I2PFragmentBase {
|
||||
//ctx.commSystem().getReachabilityStatus();
|
||||
|
||||
String status =
|
||||
"Exploratory Tunnels in/out: " + inEx + " / " + outEx
|
||||
+ "\nClient Tunnels in/out: " + inCl + " / " + outCl;
|
||||
getString(R.string.notification_status_expl, inEx, outEx) + '\n' +
|
||||
getString(R.string.notification_status_client, inCl, outCl);
|
||||
|
||||
|
||||
// Need to see if we have the participation option set to on.
|
||||
// I thought there was a router method for that? I guess not! WHY NOT?
|
||||
// It would be easier if we had a number to test status.
|
||||
String participate = "\nParticipation: " + tunnelStatus + " (" + part + ")";
|
||||
String participate = '\n' + getString(R.string.settings_label_hiddenMode) + ": " + tunnelStatus + " (" + part + ")";
|
||||
|
||||
String details =
|
||||
"\nMemory: " + DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
|
||||
+ "B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B'
|
||||
+ "\nJob Lag: " + jobLag
|
||||
+ "\nMsg Delay: " + msgDelay;
|
||||
'\n' + getString(R.string.stats_memory) + ": " +
|
||||
DataHelper.formatSize(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) +
|
||||
"B / " + DataHelper.formatSize(Runtime.getRuntime().maxMemory()) + 'B' +
|
||||
'\n' + getString(R.string.stats_lag) + ": " + jobLag +
|
||||
'\n' + getString(R.string.stats_delay) + ": " + msgDelay;
|
||||
|
||||
_savedStatus = status + participate + details;
|
||||
vAdvStatusText.setText(_savedStatus);
|
||||
@ -437,6 +448,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
// network but no router context
|
||||
vStatusContainer.setVisibility(View.GONE);
|
||||
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.INVISIBLE);
|
||||
updateState(State.STOPPED);
|
||||
/**
|
||||
* **
|
||||
* RouterService svc = _routerService; String status = "connected? "
|
||||
@ -526,12 +538,12 @@ public class MainFragment extends I2PFragmentBase {
|
||||
* compare translated nicknames - put "shared clients" first in the sort
|
||||
*/
|
||||
private class AlphaComparator implements Comparator<Destination> {
|
||||
private String xsc;
|
||||
private RouterContext _ctx;
|
||||
private final String xsc;
|
||||
private final RouterContext _ctx;
|
||||
|
||||
public AlphaComparator(RouterContext ctx) {
|
||||
_ctx = ctx;
|
||||
xsc = _(ctx, SHARED_CLIENTS);
|
||||
xsc = _t(ctx, SHARED_CLIENTS);
|
||||
}
|
||||
|
||||
public int compare(Destination lhs, Destination rhs) {
|
||||
@ -559,12 +571,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
|
||||
@ -578,16 +590,18 @@ public class MainFragment extends I2PFragmentBase {
|
||||
);
|
||||
if (language == null) {
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
// avoid ISE caused by fragment detachment ticket #2631
|
||||
final String languages[] = getResources().getStringArray(R.array.languages);
|
||||
b.setTitle(R.string.choose_language)
|
||||
.setItems(R.array.language_names, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Save the language choice
|
||||
String language = getResources().getStringArray(R.array.languages)[which];
|
||||
String language = languages[which];
|
||||
PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
.edit()
|
||||
.putString(getString(R.string.PREF_LANGUAGE), language)
|
||||
.commit();
|
||||
.apply();
|
||||
// Close the dialog
|
||||
dialog.dismiss();
|
||||
// Broadcast the change to RouterService just in case the router is running
|
||||
@ -614,16 +628,62 @@ public class MainFragment extends I2PFragmentBase {
|
||||
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
||||
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
|
||||
startActivity(hi);
|
||||
checkDialog();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i) {
|
||||
dialog.cancel();
|
||||
dialog.dismiss();
|
||||
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
||||
checkDialog();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
} else if (ab.getPref(PREF_CONFIGURE_BATTERY, true)) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
// only for Marshmallow and newer versions
|
||||
final Intent intent = new Intent();
|
||||
final Context mContext = ab.getApplicationContext();
|
||||
String packageName = mContext.getPackageName();
|
||||
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.configure_no_doze_title);
|
||||
b.setMessage(R.string.configure_no_doze);
|
||||
b.setCancelable(false);
|
||||
b.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i) {
|
||||
String packageName = mContext.getPackageName();
|
||||
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||
dialog.dismiss();
|
||||
// Simply do not re-attempt a battery optimization after the first time,
|
||||
// even if an error occurs. http://trac.i2p2.i2p/ticket/2783
|
||||
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + packageName));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
mContext.startActivity(intent);
|
||||
} catch (ActivityNotFoundException activityNotFound) {
|
||||
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||
} catch (AndroidRuntimeException activityNotFound) {
|
||||
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
b.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i) {
|
||||
dialog.cancel();
|
||||
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||
}
|
||||
});
|
||||
b.show();
|
||||
}
|
||||
} else {
|
||||
ab.setPref(PREF_CONFIGURE_BATTERY, false);
|
||||
}
|
||||
}
|
||||
/*VersionDialog dialog = new VersionDialog();
|
||||
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
|
||||
@ -648,9 +708,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);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.apps.NewsFetcher;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@ -33,12 +34,16 @@ public class NewsFragment extends I2PFragmentBase {
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
NewsFetcher nf = NewsFetcher.getInstance();
|
||||
if (nf != null) {
|
||||
// Always update the status
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
|
||||
tv.setText(nf.status().replace(" ", " "));
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx != null) {
|
||||
NewsFetcher nf = (NewsFetcher) ctx.clientAppManager().getRegisteredApp(NewsFetcher.APP_NAME);
|
||||
if (nf != null) {
|
||||
// Always update the status
|
||||
// This is the news last updated/checked text at the bottom
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
|
||||
tv.setText(nf.status().replace(" ", " "));
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
// Only update the content if we need to
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.model.I2PB64DestinationPage;
|
||||
import net.i2p.android.wizard.model.I2PDestinationPage;
|
||||
import net.i2p.android.wizard.model.PageList;
|
||||
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||
|
||||
@ -22,7 +22,7 @@ public class AddressbookAddWizardModel extends AbstractWizardModel {
|
||||
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_name))
|
||||
.setRequired(true),
|
||||
|
||||
new I2PB64DestinationPage(this, res.getString(R.string.addressbook_add_wizard_k_destination))
|
||||
new I2PDestinationPage(this, res.getString(R.string.i2ptunnel_wizard_k_dest))
|
||||
.setDescription(res.getString(R.string.addressbook_add_wizard_desc_destination))
|
||||
.setRequired(true)
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -25,16 +25,16 @@ public class AboutDialog extends DialogFragment {
|
||||
View view = li.inflate(R.layout.fragment_dialog_about, null);
|
||||
|
||||
final String currentVersion = Util.getOurVersion(getActivity());
|
||||
TextView tv = (TextView)view.findViewById(R.id.about_version);
|
||||
TextView tv = (TextView) view.findViewById(R.id.about_version);
|
||||
tv.setText(currentVersion);
|
||||
|
||||
tv = (TextView)view.findViewById(R.id.url_project);
|
||||
tv = (TextView) view.findViewById(R.id.url_project);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_android_bugs);
|
||||
tv = (TextView) view.findViewById(R.id.url_android_bugs);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_android_volunteer);
|
||||
tv = (TextView) view.findViewById(R.id.url_android_volunteer);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_donate);
|
||||
tv = (TextView) view.findViewById(R.id.url_gitlab);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
|
@ -154,6 +154,8 @@ public class NetDbDetailFragment extends I2PFragmentBase {
|
||||
String val = (String)e.getValue();
|
||||
addTableRow(table, DataHelper.stripHTML(key), DataHelper.stripHTML(val));
|
||||
}
|
||||
// spacer
|
||||
addTableRow(table, "", "");
|
||||
|
||||
addresses.addView(table);
|
||||
}
|
||||
@ -230,9 +232,11 @@ public class NetDbDetailFragment extends I2PFragmentBase {
|
||||
TextView tl1, tl2;
|
||||
|
||||
row = new TableRow(getActivity());
|
||||
// left top right bottom
|
||||
row.setPadding(10, 0, 0, 0);
|
||||
|
||||
tl1 = new TextView(getActivity());
|
||||
tl1.setPadding(0, 0, 20, 0);
|
||||
tl2 = new TextView(getActivity());
|
||||
|
||||
tl1.setText(key);
|
||||
|
@ -5,6 +5,7 @@ import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -24,14 +25,27 @@ public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
|
||||
mRouters = routers;
|
||||
}
|
||||
|
||||
/** put us on top */
|
||||
private static class RouterInfoComparator implements Comparator<RouterInfo> {
|
||||
private final Hash _us;
|
||||
|
||||
public RouterInfoComparator(Hash us) {
|
||||
_us = us;
|
||||
}
|
||||
|
||||
public int compare(RouterInfo l, RouterInfo r) {
|
||||
return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64());
|
||||
Hash lh = l.getIdentity().getHash();
|
||||
Hash rh = r.getIdentity().getHash();
|
||||
if (lh.equals(_us))
|
||||
return -1;
|
||||
if (rh.equals(_us))
|
||||
return 1;
|
||||
return lh.toBase64().compareTo(rh.toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
private class LeaseSetComparator implements Comparator<LeaseSet> {
|
||||
private RouterContext mRContext;
|
||||
private final RouterContext mRContext;
|
||||
|
||||
public LeaseSetComparator(RouterContext rContext) {
|
||||
super();
|
||||
@ -53,9 +67,9 @@ public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
|
||||
public List<NetDbEntry> loadInBackground() {
|
||||
List<NetDbEntry> ret = new ArrayList<>();
|
||||
RouterContext routerContext = Util.getRouterContext();
|
||||
if (routerContext != null && routerContext.netDb().isInitialized()) {
|
||||
if (routerContext != null && routerContext.netDb().isInitialized() && routerContext.routerHash() != null) {
|
||||
if (mRouters) {
|
||||
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
|
||||
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator(routerContext.routerHash()));
|
||||
routers.addAll(routerContext.netDb().getRouters());
|
||||
for (RouterInfo ri : routers) {
|
||||
NetDbEntry entry = NetDbEntry.fromRouterInfo(routerContext, ri);
|
||||
|
@ -17,7 +17,7 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>>> {
|
||||
private RouterContext mRContext;
|
||||
private final RouterContext mRContext;
|
||||
private List<ObjectCounter<String>> mData;
|
||||
|
||||
public NetDbStatsLoader(Context context, RouterContext rContext) {
|
||||
@ -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
|
||||
|
@ -77,6 +77,7 @@ public class NetDbSummaryTableFragment extends Fragment {
|
||||
titleRow.setPadding(10, 0, 0, 0);
|
||||
|
||||
tl1 = new TextView(getActivity());
|
||||
tl1.setPadding(0, 0, 20, 0);
|
||||
tl1.setTextSize(20);
|
||||
tl2 = new TextView(getActivity());
|
||||
tl2.setTextSize(20);
|
||||
@ -108,6 +109,7 @@ public class NetDbSummaryTableFragment extends Fragment {
|
||||
row.setPadding(10, 0, 0, 0);
|
||||
|
||||
tl1 = new TextView(getActivity());
|
||||
tl1.setPadding(0, 0, 20, 0);
|
||||
tl2 = new TextView(getActivity());
|
||||
|
||||
tl1.setText(name);
|
||||
|
@ -2,6 +2,7 @@ package net.i2p.android.router.provider;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@ -18,6 +19,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -153,7 +155,7 @@ public class CacheProvider extends ContentProvider {
|
||||
// first seg is empty since string starts with /
|
||||
String nonce = segs.length > 1 ? segs[1] : null;
|
||||
String scheme = segs.length > 2 ? segs[2] : null;
|
||||
String host = segs.length > 3 ? segs[3].toLowerCase() : null;
|
||||
String host = segs.length > 3 ? segs[3].toLowerCase(Locale.US) : null;
|
||||
String realPath = segs.length > 4 ? segs[4] : "";
|
||||
String query = uri.getEncodedQuery();
|
||||
if (query == null) {
|
||||
@ -230,10 +232,11 @@ public class CacheProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
private ParcelFileDescriptor eepFetch(Uri uri) throws FileNotFoundException {
|
||||
AppCache cache = AppCache.getInstance(getContext());
|
||||
Context ctx = getContext();
|
||||
AppCache cache = AppCache.getInstance(ctx);
|
||||
OutputStream out;
|
||||
try {
|
||||
out = cache.createCacheFile(uri);
|
||||
out = cache.createCacheFile(ctx, uri);
|
||||
} catch (IOException ioe) {
|
||||
throw new FileNotFoundException(ioe.toString());
|
||||
}
|
||||
@ -244,7 +247,7 @@ public class CacheProvider extends ContentProvider {
|
||||
File file = cache.getCacheFile(uri);
|
||||
if (file.length() > 0) {
|
||||
// this call will insert it back to us (don't set as current base)
|
||||
Uri content = cache.addCacheFile(uri, false);
|
||||
Uri content = cache.addCacheFile(ctx, uri, false);
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
} else {
|
||||
Util.d("CacheProvider Sucess but no data " + uri);
|
||||
@ -252,7 +255,7 @@ public class CacheProvider extends ContentProvider {
|
||||
} else {
|
||||
Util.d("CacheProvider Eepget fail " + uri);
|
||||
}
|
||||
AppCache.getInstance().removeCacheFile(uri);
|
||||
cache.removeCacheFile(ctx, uri);
|
||||
throw new FileNotFoundException("eepget fail");
|
||||
}
|
||||
|
||||
@ -319,7 +322,7 @@ public class CacheProvider extends ContentProvider {
|
||||
for (String key : toDelete) {
|
||||
edit.remove(key);
|
||||
}
|
||||
edit.commit();
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,11 +358,10 @@ public class CacheProvider extends ContentProvider {
|
||||
return _sharedPrefs.getString(pref, null);
|
||||
}
|
||||
|
||||
/** @return success */
|
||||
private boolean setPref(String pref, String val) {
|
||||
private void setPref(String pref, String val) {
|
||||
SharedPreferences.Editor edit = _sharedPrefs.edit();
|
||||
edit.putString(pref, val);
|
||||
return edit.commit();
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
/** @return success */
|
||||
|
@ -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;
|
||||
@ -14,12 +15,26 @@ public class OnBootReceiver extends BroadcastReceiver implements I2PConstants {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Util.e("spoofed BOOT_COMPLETED");
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean startOnBoot = prefs.getBoolean(PREF_START_ON_BOOT, false);
|
||||
|
||||
if (startOnBoot) {
|
||||
Intent routerService = new Intent(context, RouterService.class);
|
||||
context.startService(routerService);
|
||||
// Ticket #2404
|
||||
try {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
|
||||
context.startForegroundService(routerService);
|
||||
} else {
|
||||
context.startService(routerService);
|
||||
}
|
||||
} catch (IllegalStateException ex) {
|
||||
Util.e("Error: ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package net.i2p.android.router.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class RemoteStartReceiver extends BroadcastReceiver {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if(Util.getRouterContext() == null){
|
||||
Intent rsIntent = new Intent(context, RouterService.class);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
|
||||
context.startForegroundService(rsIntent);
|
||||
} else {
|
||||
context.startService(rsIntent);
|
||||
}
|
||||
Toast.makeText(context, "Starting I2P Router", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import net.i2p.android.I2PActivity;
|
||||
import net.i2p.android.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.sam.*;
|
||||
import net.i2p.sam.SAMException;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Implements SAMSecureSessionInterface on Android platforms using a Toast
|
||||
* as the interactive channel.
|
||||
*
|
||||
* @since 1.8.0
|
||||
*/
|
||||
public class AndroidSAMSecureSession extends AppCompatActivity implements SAMSecureSessionInterface {
|
||||
private static final String URI_I2P_ANDROID = "net.i2p.android";
|
||||
private final Context mCtx;
|
||||
private final RouterService _routerService;
|
||||
private final StatusBar _statusBar;
|
||||
static private Map<String, Integer> results = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
public static void affirmResult(String clientId) {
|
||||
Util.d("Affirmed result for: " + clientId);
|
||||
results.put(clientId, 1);
|
||||
}
|
||||
|
||||
public AndroidSAMSecureSession(Context ctx, RouterService rCtx, StatusBar statusBar) {
|
||||
mCtx = ctx;
|
||||
_routerService = rCtx;
|
||||
_statusBar = statusBar;
|
||||
}
|
||||
|
||||
private void waitForResult(String clientId) {
|
||||
for (int i=0;i<60;i++) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Util.e("SAMSecureSession Error", e);
|
||||
}
|
||||
Integer result = results.get(clientId);
|
||||
if (result == null)
|
||||
continue;
|
||||
if (result != -1)
|
||||
break;
|
||||
Util.d("Waiting on user to approve SAM connection for: "+clientId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isResult(String clientId) {
|
||||
waitForResult(clientId);
|
||||
final Integer finResult = results.get(clientId);
|
||||
if (finResult == null)
|
||||
return false;
|
||||
_routerService.updateStatus();
|
||||
if (finResult == 0) {
|
||||
Util.w("SAM connection cancelled by user request");
|
||||
return false;
|
||||
}
|
||||
if (finResult == 1) {
|
||||
Util.w("SAM connection allowed by user action");
|
||||
return true;
|
||||
}
|
||||
Util.w("SAM connection denied by timeout.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkResult(String clientId) {
|
||||
Intent intent = new Intent("net.i2p.android.router.service.APPROVE_SAM", null, mCtx, I2PActivity.class );
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setPackage(URI_I2P_ANDROID);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean("approveSAMConnection", true);
|
||||
bundle.putString("ID", clientId);
|
||||
intent.putExtras(bundle);
|
||||
PendingIntent pendingIntent;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
pendingIntent = PendingIntent.getActivity(
|
||||
mCtx, 7656,
|
||||
intent,
|
||||
PendingIntent.FLAG_MUTABLE,
|
||||
bundle);
|
||||
} else {
|
||||
pendingIntent = PendingIntent.getActivity(
|
||||
mCtx, 7656,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
bundle);
|
||||
}
|
||||
String dlgText = mCtx.getString(R.string.settings_confirm_sam) + "\n";//""</br>";
|
||||
dlgText += mCtx.getString(R.string.settings_confirm_sam_id) + clientId + "\n";//""</br>";
|
||||
dlgText += mCtx.getString(R.string.settings_confirm_allow_sam) + "\n";//""</br>";
|
||||
dlgText += mCtx.getString(R.string.settings_confirm_deny_sam) + "\n";//""</br>";
|
||||
|
||||
|
||||
_statusBar.replaceIntent(StatusBar.ICON_ACTIVE, dlgText, pendingIntent);
|
||||
return isResult(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean approveOrDenySecureSession(Properties i2cpProps, Properties props) throws SAMException {
|
||||
String ID = props.getProperty("USER");
|
||||
if (ID == null)
|
||||
ID = i2cpProps.getProperty("inbound.nickname");
|
||||
if (ID == null)
|
||||
ID = i2cpProps.getProperty("outbound.nickname");
|
||||
if (ID == null)
|
||||
ID = props.getProperty("ID");
|
||||
if (ID == null) {
|
||||
MessageDigest messageDigest = null;
|
||||
try {
|
||||
messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
ID = "No_ID_Present";
|
||||
}
|
||||
if (messageDigest != null) {
|
||||
String combinedProps = i2cpProps.toString() + props.toString();
|
||||
messageDigest.update(combinedProps.getBytes());
|
||||
ID = messageDigest.digest().toString();
|
||||
}else{
|
||||
ID = "No_ID_Present";
|
||||
}
|
||||
}
|
||||
return checkResult(ID);
|
||||
}
|
||||
}
|
@ -1,20 +1,30 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
|
||||
import net.i2p.BOB.BOB;
|
||||
import net.i2p.I2PAppContext;
|
||||
//import net.i2p.BOB.BOB;
|
||||
import net.i2p.addressbook.DaemonThread;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import net.i2p.android.apps.NewsFetcher;
|
||||
import net.i2p.android.router.service.AndroidSAMSecureSession;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.sam.SAMBridge;
|
||||
import net.i2p.sam.SAMSecureSessionInterface;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Load the clients we want.
|
||||
@ -22,7 +32,8 @@ import java.io.IOException;
|
||||
* We can't use LoadClientAppsJob (reading in clients.config) directly
|
||||
* because Class.forName() needs a PathClassLoader argument -
|
||||
* http://doandroids.com/blogs/2010/6/10/android-classloader-dynamic-loading-of/
|
||||
* ClassLoader cl = new PathClassLoader(_apkPath, ClassLoader.getSystemClassLoader());
|
||||
* ClassLoader cl = new PathClassLoader(_apkPath,
|
||||
* ClassLoader.getSystemClassLoader());
|
||||
*
|
||||
* We can't extend LoadClientAppsJob to specify a class loader,
|
||||
* even if we use it only for Class.forName() and not for
|
||||
@ -37,47 +48,70 @@ import java.io.IOException;
|
||||
*/
|
||||
class LoadClientsJob extends JobImpl {
|
||||
|
||||
private Context mCtx;
|
||||
private Notifications _notif;
|
||||
private final Context mCtx;
|
||||
private final RouterService _routerService;
|
||||
private final Notifications _notif;
|
||||
private DaemonThread _addressbook;
|
||||
private BOB _bob;
|
||||
public SAMBridge SAM_BRIDGE;
|
||||
private final StatusBar _statusBar;
|
||||
// private BOB _bob;
|
||||
|
||||
/** this is the delay to load (and start) the clients. */
|
||||
private static final long LOAD_DELAY = 90*1000;
|
||||
private static final long LOAD_DELAY = 60 * 1000;
|
||||
|
||||
public LoadClientsJob(Context ctx, RouterContext rCtx, Notifications notif, StatusBar status) {
|
||||
super(rCtx);
|
||||
mCtx = ctx;
|
||||
_routerService = null;
|
||||
_notif = notif;
|
||||
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
|
||||
_statusBar = status;
|
||||
}
|
||||
|
||||
public LoadClientsJob(Context ctx, RouterContext rCtx, RouterService rSvc, Notifications notif, StatusBar status) {
|
||||
super(rCtx);
|
||||
mCtx = ctx;
|
||||
_routerService = rSvc;
|
||||
_notif = notif;
|
||||
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
|
||||
_statusBar = status;
|
||||
}
|
||||
|
||||
public LoadClientsJob(Context ctx, RouterContext rCtx, Notifications notif) {
|
||||
super(rCtx);
|
||||
mCtx = ctx;
|
||||
_routerService = null;
|
||||
_notif = notif;
|
||||
getTiming().setStartAfter(getContext().clock().now() + LOAD_DELAY);
|
||||
_statusBar = null;
|
||||
}
|
||||
|
||||
public String getName() { return "Start Clients"; }
|
||||
public String getName() {
|
||||
return "Start Clients";
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
Job j = new RunI2PTunnel(getContext());
|
||||
getContext().jobQueue().addJob(j);
|
||||
Job jtunnel = new RunI2PTunnel(getContext());
|
||||
getContext().jobQueue().addJob(jtunnel);
|
||||
|
||||
Thread t = new I2PAppThread(new StatSummarizer(), "StatSummarizer", true);
|
||||
t.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
t.start();
|
||||
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(mCtx, getContext(), _notif);
|
||||
t = new I2PAppThread(fetcher, "NewsFetcher", true);
|
||||
t.start();
|
||||
|
||||
_addressbook = new DaemonThread(new String[] {"addressbook"});
|
||||
_addressbook.setName("Addressbook");
|
||||
_addressbook.setDaemon(true);
|
||||
_addressbook.start();
|
||||
|
||||
// add other clients here
|
||||
_bob = new BOB(I2PAppContext.getGlobalContext(), null, new String[0]);
|
||||
try {
|
||||
_bob.startup();
|
||||
} catch (IOException ioe) {}
|
||||
|
||||
// _bob = new BOB(I2PAppContext.getGlobalContext(), null, new String[0]);
|
||||
// try {
|
||||
// _bob.startup();
|
||||
// } catch (IOException ioe) {}
|
||||
boolean useSAM = PreferenceManager.getDefaultSharedPreferences(mCtx).getBoolean("i2pandroid.client.SAM", true);
|
||||
Util.i("SAM API " + useSAM);
|
||||
if (useSAM) {
|
||||
Job jsam = new RunI2PSAM(getContext());
|
||||
getContext().jobQueue().addJob(jsam);
|
||||
Util.i("SAM API started successfully" + useSAM);
|
||||
} else {
|
||||
Util.i("SAM API disabled, not starting " + useSAM);
|
||||
}
|
||||
getContext().addShutdownTask(new ClientShutdownHook());
|
||||
}
|
||||
|
||||
@ -87,15 +121,35 @@ class LoadClientsJob extends JobImpl {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public String getName() { return "Start I2P Tunnel"; }
|
||||
public String getName() {
|
||||
return "Start I2P Tunnel";
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
if (!getContext().router().isRunning()) {
|
||||
if (getContext().router().isAlive()) {
|
||||
requeue(1000);
|
||||
} else {
|
||||
Util.e("Router stopped before i2ptunnel could start");
|
||||
}
|
||||
return;
|
||||
}
|
||||
Util.d("Starting i2ptunnel");
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance(getContext());
|
||||
try {
|
||||
tcg.startup();
|
||||
int sz = tcg.getControllers().size();
|
||||
Util.d("i2ptunnel started " + sz + " clients");
|
||||
|
||||
// no use starting these until i2ptunnel starts
|
||||
RouterContext ctx = getContext();
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(mCtx, getContext(), _notif);
|
||||
ctx.routerAppManager().addAndStart(fetcher, new String[0]);
|
||||
|
||||
_addressbook = new DaemonThread(new String[] { "addressbook" });
|
||||
_addressbook.setName("Addressbook");
|
||||
_addressbook.setDaemon(true);
|
||||
_addressbook.start();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
Util.e("i2ptunnel failed to start", iae);
|
||||
}
|
||||
@ -103,14 +157,62 @@ class LoadClientsJob extends JobImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private class RunI2PSAM extends JobImpl {
|
||||
|
||||
public RunI2PSAM(RouterContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "Start SAM API";
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
if (!getContext().router().isRunning()) {
|
||||
if (getContext().router().isAlive()) {
|
||||
requeue(1000);
|
||||
} else {
|
||||
Util.e("Router stopped before SAM API could start");
|
||||
}
|
||||
return;
|
||||
}
|
||||
Util.d("Starting SAM");
|
||||
try {
|
||||
Util.i("Starting the SAM API");
|
||||
Looper.prepare();
|
||||
AndroidSAMSecureSession _androidSecureSession = new AndroidSAMSecureSession(mCtx, _routerService, _statusBar);
|
||||
SAMSecureSessionInterface _secureSession = _androidSecureSession;
|
||||
SAM_BRIDGE = new SAMBridge("127.0.0.1",
|
||||
7656,
|
||||
false,
|
||||
SAM_PROPERTIES(),
|
||||
"sam.keys",
|
||||
new File("sam_config"),
|
||||
_secureSession);
|
||||
SAM_BRIDGE.run();
|
||||
} catch (IOException e) {
|
||||
Util.e(e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Properties SAM_PROPERTIES() throws IOException {
|
||||
Util.i("Getting the default properties");
|
||||
Properties sam_properties = new Properties();
|
||||
return sam_properties;
|
||||
}
|
||||
}
|
||||
|
||||
private class ClientShutdownHook implements Runnable {
|
||||
public void run() {
|
||||
Util.d("client shutdown hook");
|
||||
// i2ptunnel registers its own hook
|
||||
// StatSummarizer registers its own hook
|
||||
// NewsFetcher registers its own hook
|
||||
if (_bob != null)
|
||||
_bob.shutdown(null);
|
||||
// if (_bob != null)
|
||||
// _bob.shutdown(null);
|
||||
if (SAM_BRIDGE != null)
|
||||
SAM_BRIDGE.shutdown(null);
|
||||
if (_addressbook != null)
|
||||
_addressbook.halt();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
@ -23,7 +24,6 @@ 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;
|
||||
|
||||
@ -106,7 +106,7 @@ public class RouterService extends Service {
|
||||
if(lastState == State.RUNNING || lastState == State.ACTIVE) {
|
||||
Intent intent = new Intent(this, RouterService.class);
|
||||
intent.putExtra(EXTRA_RESTART, true);
|
||||
onStartCommand(intent, 12345, 67890);
|
||||
onStartCommand(intent, START_FLAG_REDELIVERY | START_FLAG_RETRY, 67890);
|
||||
} else if(lastState == State.MANUAL_QUITTING || lastState == State.GRACEFUL_SHUTDOWN) {
|
||||
synchronized(_stateLock) {
|
||||
setState(State.MANUAL_QUITTED);
|
||||
@ -169,10 +169,18 @@ public class RouterService extends Service {
|
||||
}
|
||||
_handler.removeCallbacks(_updater);
|
||||
_handler.postDelayed(_updater, 50);
|
||||
|
||||
// We need to *not* check if we're restarting on Android greater than Oreo due to
|
||||
// changes in how notifications work and the use of NotificationChannels.
|
||||
if(!restart) {
|
||||
startForeground(1337, _statusBar.getNote());
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
startForeground(1337, _statusBar.getNote());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//return START_STICKY;
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
@ -213,19 +221,22 @@ 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.setUPnPScannerCallback(new SSDPLocker(RouterService.this));
|
||||
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);
|
||||
Job loadJob = new LoadClientsJob(RouterService.this, _context, RouterService.this, _notif, _statusBar);
|
||||
_context.jobQueue().addJob(loadJob);
|
||||
_context.addShutdownTask(new ShutdownHook());
|
||||
_context.addFinalShutdownTask(new FinalShutdownHook());
|
||||
@ -251,6 +262,16 @@ public class RouterService extends Service {
|
||||
private String _currTitle;
|
||||
private boolean _hadTunnels;
|
||||
|
||||
public void updateStatus() {
|
||||
RouterContext ctx = _context;
|
||||
if(ctx != null && (_state == State.RUNNING || _state == State.ACTIVE || _state == State.GRACEFUL_SHUTDOWN)) {
|
||||
Router router = ctx.router();
|
||||
if(router.isAlive()) {
|
||||
updateStatus(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatus(RouterContext ctx) {
|
||||
int active = ctx.commSystem().countActivePeers();
|
||||
int known = Math.max(ctx.netDb().getKnownRouters() - 1, 0);
|
||||
@ -793,12 +814,12 @@ public class RouterService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
* Saves state in background thread
|
||||
*/
|
||||
private boolean saveState() {
|
||||
private void saveState() {
|
||||
SharedPreferences prefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putString(LAST_STATE, _state.toString());
|
||||
return edit.commit();
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.net.wifi.WifiManager.MulticastLock;
|
||||
|
||||
import net.i2p.router.transport.UPnPScannerCallback;
|
||||
|
||||
/**
|
||||
* To lock/unlock UPnP, so it works on some phones.
|
||||
* Many many phones don't require this, but do be safe...
|
||||
*
|
||||
* @since 0.9.41
|
||||
*/
|
||||
public class SSDPLocker implements UPnPScannerCallback {
|
||||
|
||||
private final MulticastLock lock;
|
||||
|
||||
public SSDPLocker(Context context) {
|
||||
WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
lock = wifi.createMulticastLock("ssdp");
|
||||
lock.setReferenceCounted(false);
|
||||
}
|
||||
|
||||
public void beforeScan() {
|
||||
lock.acquire();
|
||||
}
|
||||
|
||||
public void afterScan() {
|
||||
if (lock.isHeld())
|
||||
lock.release();
|
||||
}
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.I2PActivity;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
|
||||
class StatusBar {
|
||||
|
||||
@ -16,6 +21,9 @@ class StatusBar {
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final NotificationCompat.Builder mNotifyBuilder;
|
||||
private Notification mNotif;
|
||||
private final String NOTIFICATION_CHANNEL_ID = "net.i2p.android.STARTUP_STATE_CHANNEL";
|
||||
private final String channelName = "I2P Router Service";
|
||||
NotificationChannel mNotificationChannel;
|
||||
|
||||
private static final int ID = 1337;
|
||||
|
||||
@ -28,39 +36,90 @@ class StatusBar {
|
||||
|
||||
StatusBar(Context ctx) {
|
||||
mCtx = ctx;
|
||||
mNotificationManager = (NotificationManager) ctx.getSystemService(
|
||||
mNotificationManager = (NotificationManager) mCtx.getSystemService(
|
||||
Context.NOTIFICATION_SERVICE);
|
||||
assert mNotificationManager != null;
|
||||
|
||||
Thread.currentThread().setUncaughtExceptionHandler(
|
||||
new CrashHandler(mNotificationManager));
|
||||
|
||||
int icon = ICON_STARTING;
|
||||
// won't be shown if replace() is called
|
||||
String text = ctx.getString(R.string.notification_status_starting);
|
||||
String text = mCtx.getString(R.string.notification_status_starting);
|
||||
mNotifyBuilder = notifyBuilder(icon, text);
|
||||
}
|
||||
|
||||
mNotifyBuilder = new NotificationCompat.Builder(ctx)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(icon)
|
||||
.setColor(mCtx.getResources().getColor(R.color.primary_light))
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true);
|
||||
private NotificationCompat.Builder notifyBuilder(int icon, String text) {
|
||||
NotificationCompat.Builder tNotifyBuilder;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
tNotifyBuilder = new NotificationCompat.Builder(mCtx);
|
||||
} else {
|
||||
tNotifyBuilder = new NotificationCompat.Builder(mCtx, NOTIFICATION_CHANNEL_ID);
|
||||
}
|
||||
tNotifyBuilder.setContentText(text);
|
||||
tNotifyBuilder.setSmallIcon(icon);
|
||||
tNotifyBuilder.setColor(mCtx.getResources().getColor(R.color.primary_light));
|
||||
tNotifyBuilder.setOngoing(true);
|
||||
tNotifyBuilder.setPriority(NotificationManager.IMPORTANCE_LOW);
|
||||
tNotifyBuilder.setCategory(Notification.CATEGORY_SERVICE);
|
||||
|
||||
Intent intent = new Intent(ctx, I2PActivity.class);
|
||||
PendingIntent pi = this.pendingIntent();
|
||||
tNotifyBuilder.setContentIntent(pi);
|
||||
return tNotifyBuilder;
|
||||
}
|
||||
|
||||
private PendingIntent pendingIntent() {
|
||||
Intent intent = new Intent(mCtx, I2PActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
mNotifyBuilder.setContentIntent(pi);
|
||||
PendingIntent pi = null;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
pi = PendingIntent.getActivity(mCtx,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_MUTABLE);
|
||||
} else {
|
||||
pi = PendingIntent.getActivity(mCtx,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
public void replace(int icon, int textResource) {
|
||||
PendingIntent pi = pendingIntent();
|
||||
mNotifyBuilder.setContentIntent(pi);
|
||||
replace(icon, mCtx.getString(textResource));
|
||||
}
|
||||
|
||||
public void replace(int icon, String title) {
|
||||
PendingIntent pi = pendingIntent();
|
||||
mNotifyBuilder.setContentIntent(pi);
|
||||
mNotifyBuilder.setSmallIcon(icon)
|
||||
.setStyle(null)
|
||||
.setTicker(title);
|
||||
update(title);
|
||||
}
|
||||
|
||||
public void replaceIntent(int icon, String text, PendingIntent pi) {
|
||||
mNotifyBuilder.setContentIntent(pi);
|
||||
mNotifyBuilder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||
mNotifyBuilder.setAutoCancel(true);
|
||||
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
|
||||
mNotifyBuilder.setSmallIcon(icon).setContentText(text);
|
||||
update(null, text);
|
||||
}
|
||||
|
||||
public void replaceIntent(int icon, int textResource, PendingIntent pi) {
|
||||
String text = mCtx.getString(textResource);
|
||||
mNotifyBuilder.setContentIntent(pi);
|
||||
mNotifyBuilder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||
mNotifyBuilder.setAutoCancel(true);
|
||||
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
|
||||
mNotifyBuilder.setSmallIcon(icon).setContentText(text);
|
||||
update(null, text);
|
||||
}
|
||||
|
||||
public void update(String title) {
|
||||
update(title, null);
|
||||
}
|
||||
@ -72,8 +131,16 @@ class StatusBar {
|
||||
}
|
||||
|
||||
public void update(String title, String text) {
|
||||
mNotifyBuilder.setContentTitle(title)
|
||||
.setContentText(text);
|
||||
if (title != null)
|
||||
mNotifyBuilder.setContentTitle(title);
|
||||
if (text != null)
|
||||
mNotifyBuilder.setContentText(text);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
|
||||
mNotificationManager.createNotificationChannel(mNotificationChannel);
|
||||
mNotificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
mNotifyBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
||||
}
|
||||
mNotif = mNotifyBuilder.build();
|
||||
mNotificationManager.notify(ID, mNotif);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -11,9 +11,10 @@ import android.view.ViewGroup;
|
||||
import com.androidplot.xy.BarFormatter;
|
||||
import com.androidplot.xy.BarRenderer;
|
||||
import com.androidplot.xy.BoundaryMode;
|
||||
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;
|
||||
@ -120,26 +121,25 @@ public class RateGraphFragment extends I2PFragmentBase {
|
||||
|
||||
_ratePlot.addSeries(rateSeries, new BarFormatter(Color.argb(200, 0, 80, 0), Color.argb(200, 0, 80, 0)));
|
||||
_ratePlot.calculateMinMaxVals();
|
||||
long maxX = _ratePlot.getCalculatedMaxX().longValue();
|
||||
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);
|
||||
|
||||
BarRenderer renderer = (BarRenderer) _ratePlot.getRenderer(BarRenderer.class);
|
||||
renderer.setBarWidthStyle(BarRenderer.BarWidthStyle.VARIABLE_WIDTH);
|
||||
renderer.setBarGap(0);
|
||||
BarRenderer renderer = _ratePlot.getRenderer(BarRenderer.class);
|
||||
renderer.setBarGroupWidth(BarRenderer.BarGroupWidthMode.FIXED_GAP, 0);
|
||||
|
||||
_ratePlot.setDomainUpperBoundary(maxX, BoundaryMode.GROW);
|
||||
_ratePlot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 15 * 60 * 1000);
|
||||
_ratePlot.setTicksPerDomainLabel(4);
|
||||
_ratePlot.setDomainStep(StepMode.INCREMENT_BY_VAL, 15 * 60 * 1000);
|
||||
_ratePlot.setLinesPerDomainLabel(4);
|
||||
|
||||
_ratePlot.setRangeLowerBoundary(0, BoundaryMode.FIXED);
|
||||
_ratePlot.setTicksPerRangeLabel(5);
|
||||
_ratePlot.setLinesPerRangeLabel(5);
|
||||
|
||||
_ratePlot.setDomainValueFormat(new Format() {
|
||||
_ratePlot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).setFormat(new Format() {
|
||||
private DateFormat dateFormat = SimpleDateFormat.getTimeInstance(DateFormat.SHORT);
|
||||
|
||||
@Override
|
||||
@ -157,13 +157,13 @@ public class RateGraphFragment extends I2PFragmentBase {
|
||||
});
|
||||
|
||||
final int finalK = _k;
|
||||
_ratePlot.setRangeValueFormat(new Format() {
|
||||
_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();
|
||||
double maxY = _ratePlot.getCalculatedMaxY().doubleValue();
|
||||
double maxY = _ratePlot.getBounds().getMaxY().doubleValue();
|
||||
|
||||
if (val == 0 || maxY < finalK) {
|
||||
return new DecimalFormat("0").format(val, toAppendTo, pos);
|
||||
@ -194,8 +194,8 @@ public class RateGraphFragment extends I2PFragmentBase {
|
||||
|
||||
private void updatePlot() {
|
||||
_ratePlot.calculateMinMaxVals();
|
||||
double maxY = _ratePlot.getCalculatedMaxY().doubleValue();
|
||||
_ratePlot.setRangeStep(XYStepMode.INCREMENT_BY_VAL, getRangeStep(maxY, _k));
|
||||
double maxY = _ratePlot.getBounds().getMaxY().doubleValue();
|
||||
_ratePlot.setRangeStep(StepMode.INCREMENT_BY_VAL, getRangeStep(maxY, _k));
|
||||
|
||||
_ratePlot.redraw();
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ public class AppCache {
|
||||
private static AppCache _instance;
|
||||
private static File _cacheDir;
|
||||
private static long _totalSize;
|
||||
private static ContentResolver _resolver;
|
||||
/** the LRU cache */
|
||||
private final Map<Integer, Object> _cache;
|
||||
|
||||
@ -62,7 +61,6 @@ public class AppCache {
|
||||
_cacheDir = new File(ctx.getCacheDir(), DIR_NAME);
|
||||
_cacheDir.mkdir();
|
||||
Util.d("AppCache cache dir " + _cacheDir);
|
||||
_resolver = ctx.getContentResolver();
|
||||
_cache = new LHM(MAX_FILES);
|
||||
initialize();
|
||||
}
|
||||
@ -72,9 +70,9 @@ public class AppCache {
|
||||
* addCacheFile() or removeCacheFile() after the data is written.
|
||||
* @param key no fragment allowed
|
||||
*/
|
||||
public OutputStream createCacheFile(Uri key) throws IOException {
|
||||
public OutputStream createCacheFile(Context ctx, Uri key) throws IOException {
|
||||
// remove any old file so the total stays correct
|
||||
removeCacheFile(key);
|
||||
removeCacheFile(ctx, key);
|
||||
File f = toFile(key);
|
||||
f.getParentFile().mkdirs();
|
||||
return new FileOutputStream(f);
|
||||
@ -88,7 +86,7 @@ public class AppCache {
|
||||
* @param key no fragment allowed
|
||||
* @param setAsCurrentBase tell CacheProvider
|
||||
*/
|
||||
public Uri addCacheFile(Uri key, boolean setAsCurrentBase) {
|
||||
public Uri addCacheFile(Context ctx, Uri key, boolean setAsCurrentBase) {
|
||||
int hash = toHash(key);
|
||||
synchronized(_cache) {
|
||||
_cache.put(hash, DUMMY);
|
||||
@ -96,19 +94,19 @@ public class AppCache {
|
||||
// file:/// uri
|
||||
//return Uri.fromFile(toFile(hash)).toString();
|
||||
// content:// uri
|
||||
return insertContent(key, setAsCurrentBase);
|
||||
return insertContent(ctx, key, setAsCurrentBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a previously written file from the cache index and disk.
|
||||
* @param key no fragment allowed
|
||||
*/
|
||||
public void removeCacheFile(Uri key) {
|
||||
public void removeCacheFile(Context ctx, Uri key) {
|
||||
int hash = toHash(key);
|
||||
synchronized(_cache) {
|
||||
_cache.remove(hash);
|
||||
}
|
||||
deleteContent(key);
|
||||
deleteContent(ctx, key);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,7 +116,7 @@ public class AppCache {
|
||||
*
|
||||
* @param key no fragment allowed
|
||||
*/
|
||||
public Uri getCacheUri(Uri key) {
|
||||
public Uri getCacheUri(Context ctx, Uri key) {
|
||||
int hash = toHash(key);
|
||||
// poke the LRU
|
||||
Object present;
|
||||
@ -126,7 +124,7 @@ public class AppCache {
|
||||
present = _cache.get(hash);
|
||||
}
|
||||
if (present != null)
|
||||
setAsCurrentBase(key);
|
||||
setAsCurrentBase(ctx, key);
|
||||
return CacheProvider.getContentUri(key);
|
||||
}
|
||||
|
||||
@ -242,7 +240,7 @@ public class AppCache {
|
||||
/**
|
||||
* @return the uri inserted or null on failure
|
||||
*/
|
||||
private static Uri insertContent(Uri key, boolean setAsCurrentBase) {
|
||||
private static Uri insertContent(Context ctx, Uri key, boolean setAsCurrentBase) {
|
||||
String path = toFile(key).getAbsolutePath();
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(CacheProvider.DATA, path);
|
||||
@ -250,8 +248,9 @@ public class AppCache {
|
||||
cv.put(CacheProvider.CURRENT_BASE, Boolean.TRUE);
|
||||
Uri uri = CacheProvider.getContentUri(key);
|
||||
if (uri != null) {
|
||||
_resolver.insert(uri, cv);
|
||||
return uri;
|
||||
ContentResolver resolver = ctx.getContentResolver();
|
||||
resolver.insert(uri, cv);
|
||||
return uri;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -259,19 +258,23 @@ public class AppCache {
|
||||
/**
|
||||
* Set key as current base. May be content or i2p key.
|
||||
*/
|
||||
private static void setAsCurrentBase(Uri key) {
|
||||
private static void setAsCurrentBase(Context ctx, Uri key) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(CacheProvider.CURRENT_BASE, Boolean.TRUE);
|
||||
Uri uri = CacheProvider.getContentUri(key);
|
||||
if (uri != null)
|
||||
_resolver.insert(uri, cv);
|
||||
if (uri != null) {
|
||||
ContentResolver resolver = ctx.getContentResolver();
|
||||
resolver.insert(uri, cv);
|
||||
}
|
||||
}
|
||||
|
||||
/** ok for now but we will need to store key in the map and delete by integer */
|
||||
private static void deleteContent(Uri key) {
|
||||
private static void deleteContent(Context ctx, Uri key) {
|
||||
Uri uri = CacheProvider.getContentUri(key);
|
||||
if (uri != null)
|
||||
_resolver.delete(uri, null, null);
|
||||
if (uri != null) {
|
||||
ContentResolver resolver = ctx.getContentResolver();
|
||||
resolver.delete(uri, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,20 +32,50 @@ 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 + ')';
|
||||
|
||||
String destB64 = data.getBundle(kDest).getString(Page.SIMPLE_DATA_KEY);
|
||||
Destination dest = new Destination();
|
||||
try {
|
||||
dest.fromBase64(destB64);
|
||||
} catch (DataFormatException e) {} // Already validated
|
||||
|
||||
String dest = data.getBundle(kDest).getString(Page.SIMPLE_DATA_KEY).split(":")[0];
|
||||
Destination destination = new Destination();
|
||||
if (dest.endsWith(".b32.i2p")) {
|
||||
NamingService dns = NamingServiceUtil.getNamingService(Util.getRouterContext(),"");
|
||||
destination = dns.lookup(dest);
|
||||
int i = 0;
|
||||
while (destination == null) {
|
||||
dns = NamingServiceUtil.getNamingService(Util.getRouterContext(),"");
|
||||
destination = dns.lookup(dest);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
i++;
|
||||
if (i > 500){
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
destination.fromBase64(dest);
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// Check if already in addressbook
|
||||
Destination oldDest = ns.lookup(host);
|
||||
if (oldDest != null) {
|
||||
if (destB64.equals(oldDest.toBase64()))
|
||||
if (destination.toBase64().equals(oldDest.toBase64()))
|
||||
Toast.makeText(ctx,
|
||||
"Host name " + displayHost + " is already in address book, unchanged.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
@ -55,7 +85,7 @@ public class NamingServiceUtil {
|
||||
Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
// Put the new host name
|
||||
success = ns.put(host, dest);
|
||||
success = ns.put(host, destination);
|
||||
if (!success)
|
||||
Toast.makeText(ctx,
|
||||
"Failed to add Destination " + displayHost + " to naming service " + ns.getName(),
|
||||
|
@ -2,16 +2,20 @@ package net.i2p.android.router.util;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
public class Notifications {
|
||||
private final Context mCtx;
|
||||
private final NotificationManager mNotificationManager;
|
||||
|
||||
|
||||
|
||||
public static final int ICON = R.drawable.ic_stat_router_active;
|
||||
|
||||
public Notifications(Context ctx) {
|
||||
@ -25,18 +29,36 @@ 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)
|
||||
.setColor(mCtx.getResources().getColor(R.color.primary_light))
|
||||
.setAutoCancel(true);
|
||||
notify(title, text, "", c);
|
||||
}
|
||||
|
||||
public void notify(String title, String text, String channel, Class<?> c) {
|
||||
NotificationCompat.Builder b;
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
b = new NotificationCompat.Builder(mCtx);
|
||||
} else {
|
||||
if (channel.equals("")){
|
||||
b = new NotificationCompat.Builder(mCtx);
|
||||
} else {
|
||||
b = new NotificationCompat.Builder(mCtx, channel);
|
||||
}
|
||||
}
|
||||
|
||||
b.setContentTitle(title);
|
||||
b.setContentText(text);
|
||||
b.setSmallIcon(ICON);
|
||||
b.setColor(mCtx.getResources().getColor(R.color.primary_light));
|
||||
b.setAutoCancel(true);
|
||||
|
||||
if (c != null) {
|
||||
Intent intent = new Intent(mCtx, c);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pi = PendingIntent.getActivity(mCtx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent pi;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
pi = PendingIntent.getActivity(mCtx, 0, intent, PendingIntent.FLAG_MUTABLE);
|
||||
} else {
|
||||
pi = PendingIntent.getActivity(mCtx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
b.setContentIntent(pi);
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -185,9 +186,9 @@ public abstract class Util implements I2PConstants {
|
||||
toRemove.setProperty("routerconsole.lang", "");
|
||||
toRemove.setProperty("routerconsole.country", "");
|
||||
} else {
|
||||
routerProps.setProperty("routerconsole.lang", language[0].toLowerCase());
|
||||
routerProps.setProperty("routerconsole.lang", language[0].toLowerCase(Locale.US));
|
||||
if (language.length == 2)
|
||||
routerProps.setProperty("routerconsole.country", language[1].toUpperCase());
|
||||
routerProps.setProperty("routerconsole.country", language[1].toUpperCase(Locale.US));
|
||||
else
|
||||
toRemove.setProperty("routerconsole.country", "");
|
||||
}
|
||||
@ -299,7 +300,15 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -437,10 +446,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?
|
||||
@ -454,7 +468,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:
|
||||
@ -475,7 +489,7 @@ public abstract class Util implements I2PConstants {
|
||||
case IPV4_UNKNOWN_IPV6_FIREWALLED:
|
||||
case IPV4_DISABLED_IPV6_UNKNOWN:
|
||||
default:
|
||||
ra = routerInfo.getTargetAddress("SSU");
|
||||
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));
|
||||
|
@ -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;
|
||||
@ -26,9 +28,11 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
public class I2PWebViewClient extends WebViewClient {
|
||||
|
||||
private final Fragment _parentFrag;
|
||||
private BGLoad _lastTask;
|
||||
/** save it here so we can dismiss it in onPageFinished() */
|
||||
private ProgressDialog _lastDialog;
|
||||
@ -40,6 +44,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);
|
||||
@ -68,7 +77,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
fail(view, "Bad URL " + url);
|
||||
return true;
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
s = s.toLowerCase(Locale.US);
|
||||
if (!(s.equals("http") || s.equals("https") ||
|
||||
s.equals(CONTENT))) {
|
||||
Util.d("Not loading URL " + url);
|
||||
@ -85,7 +94,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
h = h.toLowerCase();
|
||||
h = h.toLowerCase(Locale.US);
|
||||
if (h.endsWith(".i2p")) {
|
||||
if (!s.equals("http")) {
|
||||
fail(view, "Bad URL " + url);
|
||||
@ -102,7 +111,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);
|
||||
@ -203,7 +212,8 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
//reverse back to a i2p URI so we can delete it from the AppCache
|
||||
uri = CacheProvider.getI2PUri(uri);
|
||||
Util.d("clearing AppCache entry for current page " + uri);
|
||||
AppCache.getInstance(view.getContext()).removeCacheFile(uri);
|
||||
Context ctx = view.getContext();
|
||||
AppCache.getInstance(ctx).removeCacheFile(ctx, uri);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// this actually only deletes the row in the provider,
|
||||
// not the actual file, but it will be overwritten in the reload.
|
||||
@ -215,10 +225,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 +317,10 @@ 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);
|
||||
final AppCache cache = AppCache.getInstance(_ctx);
|
||||
File cacheFile = cache.getCacheFile(uri);
|
||||
if (cacheFile.exists()) {
|
||||
final Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
|
||||
final Uri resUri = cache.getCacheUri(_ctx, uri);
|
||||
Util.d("Loading " + url + " from resource cache " + resUri);
|
||||
_view.post(new Runnable() {
|
||||
@Override
|
||||
@ -325,7 +338,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
//EepGetFetcher fetcher = new EepGetFetcher(url);
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = AppCache.getInstance(_view.getContext()).createCacheFile(uri);
|
||||
out = cache.createCacheFile(_ctx, uri);
|
||||
// write error to stream
|
||||
EepGetFetcher fetcher = new EepGetFetcher(url, out, true);
|
||||
fetcher.addStatusListener(this);
|
||||
@ -338,11 +351,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 = cache.addCacheFile(_ctx, uri, true);
|
||||
if (content != null) {
|
||||
Util.d("Stored cache in " + content);
|
||||
} else {
|
||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
||||
cache.removeCacheFile(_ctx, uri);
|
||||
Util.d("cache create error");
|
||||
return 0;
|
||||
}
|
||||
@ -381,7 +394,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
||||
cache.removeCacheFile(_ctx, uri);
|
||||
Util.d("loading error data URL: " + url);
|
||||
final String finalMsg = msg;
|
||||
_view.post(new Runnable() {
|
||||
@ -403,30 +416,36 @@ 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 (RuntimeException iae) {
|
||||
// throws IAE - not attached to window manager - perhaps due to screen rotation?
|
||||
// Also includes android.view.WindowManager$BadTokenException extends RuntimeException
|
||||
Util.e("Error while updating I2PWebViewClient dialog", iae);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.eowise.recyclerview.stickyheaders.StickyHeadersAdapter;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
@ -49,7 +51,7 @@ public class AlphanumericHeaderAdapter implements StickyHeadersAdapter<Alphanume
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder headerViewHolder, int position) {
|
||||
String sortString = mAdapter.getSortString(position).toUpperCase();
|
||||
String sortString = mAdapter.getSortString(position).toUpperCase(Locale.getDefault());
|
||||
if (sortString.isEmpty())
|
||||
headerViewHolder.itemView.setVisibility(View.GONE);
|
||||
else {
|
||||
@ -63,7 +65,7 @@ public class AlphanumericHeaderAdapter implements StickyHeadersAdapter<Alphanume
|
||||
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
String sortString = mAdapter.getSortString(position).toUpperCase();
|
||||
String sortString = mAdapter.getSortString(position).toUpperCase(Locale.getDefault());
|
||||
if (sortString.isEmpty())
|
||||
return Integer.MAX_VALUE;
|
||||
|
||||
|
@ -3,19 +3,17 @@ package net.i2p.android.util;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class MemoryFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||
private FragmentManager mFragmentManager;
|
||||
private Map<Integer, String> mFragmentTags;
|
||||
private final FragmentManager mFragmentManager;
|
||||
private final SparseArray<String> mFragmentTags;
|
||||
|
||||
public MemoryFragmentPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
mFragmentManager = fm;
|
||||
mFragmentTags = new HashMap<>();
|
||||
mFragmentTags = new SparseArray<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
package net.i2p.android.wizard.model;
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import net.i2p.android.wizard.ui.I2PB64DestinationFragment;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* A page asking for an I2P Destination.
|
||||
* This must be the B64 representation of a Destination.
|
||||
*/
|
||||
public class I2PB64DestinationPage extends SingleTextFieldPage {
|
||||
private String mFeedback;
|
||||
|
||||
public I2PB64DestinationPage(ModelCallbacks callbacks, String title) {
|
||||
super(callbacks, title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment createFragment() {
|
||||
return I2PB64DestinationFragment.create(getKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
String data = mData.getString(SIMPLE_DATA_KEY);
|
||||
try {
|
||||
new Destination().fromBase64(data);
|
||||
} catch (DataFormatException dfe) {
|
||||
mFeedback = "Invalid B64";
|
||||
return false;
|
||||
}
|
||||
mFeedback = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showFeedback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeedback() {
|
||||
return mFeedback;
|
||||
}
|
||||
}
|
@ -4,9 +4,12 @@ import java.util.Locale;
|
||||
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.wizard.ui.I2PDestinationFragment;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.naming.DummyNamingService;
|
||||
|
||||
/**
|
||||
* A page asking for an I2P Destination.
|
||||
@ -27,12 +30,20 @@ public class I2PDestinationPage extends SingleTextFieldPage {
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
String data = mData.getString(SIMPLE_DATA_KEY);
|
||||
String data = mData.getString(SIMPLE_DATA_KEY).split(":")[0];
|
||||
if (data.toLowerCase(Locale.US).endsWith(".b32.i2p")) { /* B32 */
|
||||
if (data.length() != BASE32_HASH_LENGTH + 8) {
|
||||
mFeedback = "Invalid B32";
|
||||
return false;
|
||||
if (data.length() == BASE32_HASH_LENGTH){
|
||||
mFeedback = "";
|
||||
return true;
|
||||
}
|
||||
if (data.length() >= BASE32_HASH_LENGTH + 8) {
|
||||
if (data.length() <= BASE32_HASH_LENGTH + 12) {
|
||||
mFeedback = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
mFeedback = "Invalid B32";
|
||||
return false;
|
||||
} else if (data.endsWith(".i2p")) { /* Domain */
|
||||
// Valid
|
||||
} else if (data.length() >= 516) { /* B64 */
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 str4d
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.i2p.android.wizard.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.android.wizard.model.Page;
|
||||
import net.i2p.android.wizard.model.SingleTextFieldPage;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class I2PB64DestinationFragment extends Fragment {
|
||||
static final int REQUEST_DESTINATION_FILE = 1;
|
||||
|
||||
private static final String ARG_KEY = "key";
|
||||
|
||||
private PageFragmentCallbacks mCallbacks;
|
||||
private SingleTextFieldPage mPage;
|
||||
protected TextView mFieldView;
|
||||
private TextView mFeedbackView;
|
||||
|
||||
public static I2PB64DestinationFragment create(String key) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_KEY, key);
|
||||
|
||||
I2PB64DestinationFragment fragment = new I2PB64DestinationFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public I2PB64DestinationFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
String mKey = args.getString(ARG_KEY);
|
||||
mPage = (SingleTextFieldPage) mCallbacks.onGetPage(mKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_wizard_page_single_text_field_picker, container, false);
|
||||
((TextView) rootView.findViewById(android.R.id.title)).setText(mPage.getTitle());
|
||||
((TextView) rootView.findViewById(R.id.wizard_text_field_desc)).setText(mPage.getDesc());
|
||||
|
||||
Button b = (Button) rootView.findViewById(R.id.wizard_text_field_pick);
|
||||
b.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
i.setType("text/plain");
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
try {
|
||||
startActivityForResult(
|
||||
Intent.createChooser(i,"Select B64 file"),
|
||||
REQUEST_DESTINATION_FILE);
|
||||
} catch (android.content.ActivityNotFoundException ex) {
|
||||
Toast.makeText(getActivity(), "Please install a File Manager.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mFieldView = ((TextView) rootView.findViewById(R.id.wizard_text_field));
|
||||
mFieldView.setHint(mPage.getTitle());
|
||||
if (mPage.getData().getString(Page.SIMPLE_DATA_KEY) != null)
|
||||
mFieldView.setText(mPage.getData().getString(Page.SIMPLE_DATA_KEY));
|
||||
else if (mPage.getDefault() != null) {
|
||||
mFieldView.setText(mPage.getDefault());
|
||||
mPage.getData().putString(Page.SIMPLE_DATA_KEY, mPage.getDefault());
|
||||
}
|
||||
|
||||
mFeedbackView = (TextView) rootView.findViewById(R.id.wizard_text_field_feedback);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
if (!(activity instanceof PageFragmentCallbacks)) {
|
||||
throw new ClassCastException("Activity must implement PageFragmentCallbacks");
|
||||
}
|
||||
|
||||
mCallbacks = (PageFragmentCallbacks) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mCallbacks = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mFieldView.addTextChangedListener(new TextWatcher() {
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1,
|
||||
int i2) {
|
||||
}
|
||||
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
public void afterTextChanged(Editable editable) {
|
||||
mPage.getData().putString(Page.SIMPLE_DATA_KEY,
|
||||
(editable != null) ? editable.toString() : null);
|
||||
mPage.notifyDataChanged();
|
||||
if (mPage.showFeedback()) {
|
||||
mFeedbackView.setText(mPage.getFeedback());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuVisibility(boolean menuVisible) {
|
||||
super.setMenuVisibility(menuVisible);
|
||||
|
||||
// In a future update to the support library, this should override setUserVisibleHint
|
||||
// instead of setMenuVisibility.
|
||||
if (mFieldView != null) {
|
||||
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
if (!menuVisible) {
|
||||
imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_DESTINATION_FILE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri result = data.getData();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
ParcelFileDescriptor pfd = getActivity().getContentResolver().openFileDescriptor(result, "r");
|
||||
br = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new ParcelFileDescriptor.AutoCloseInputStream(pfd)));
|
||||
try {
|
||||
mFieldView.setText(br.readLine());
|
||||
} catch (IOException ioe) {
|
||||
Util.e("Failed to read B64 file", ioe);
|
||||
Toast.makeText(getActivity(), "Failed to read B64 file.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
Util.e("Could not find B64 file", fnfe);
|
||||
Toast.makeText(getActivity(), "Could not find B64 file.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} finally {
|
||||
if (br != null)
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 423 B |
Before Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 246 B |
Before Width: | Height: | Size: 535 B |
Before Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 283 B |
Before Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 282 B |
Before Width: | Height: | Size: 531 B |
Before Width: | Height: | Size: 650 B |
Before Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 767 B |
Before Width: | Height: | Size: 331 B |
Before Width: | Height: | Size: 640 B |