Compare commits
176 Commits
android-0.
...
android-0.
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
19036a71cb | |||
40f3fbf9c5 | |||
1127fb0195 | |||
df81efe6bc | |||
784ca3691b | |||
0fa4241ce6 | |||
5063d276de | |||
81d0e43f0f | |||
1637a9007d | |||
ce0f01cf46 | |||
dd579d4f5b | |||
5703d8cc6d | |||
b8768ae9fe | |||
54dc2c88bf | |||
dba01b8c18 | |||
b7b3eb7019 | |||
430e2ab826 | |||
87383a2ec8 | |||
f63bfe1dea | |||
ff2021c0aa | |||
51f7e07080 | |||
7797e067a5 | |||
cf09a21f1e | |||
914294927d | |||
bd0455c413 | |||
97f3d937ee | |||
ff102bfe73 | |||
e31a350398 | |||
43a8f29794 | |||
bbca783b20 | |||
6d4fe52f8e | |||
ecb08a54fb | |||
7bd4524fd8 | |||
40f08d56f6 | |||
fe61e35146 | |||
be3f74d71f | |||
8dcfa816e3 | |||
ae05e22670 | |||
79a4fa0407 | |||
bb958b969a | |||
02030454d1 | |||
e396b0b614 | |||
91cac6b743 | |||
2a6015d890 | |||
049b094627 | |||
077d062e19 | |||
4ad483db71 | |||
3edb8ad0c2 | |||
769f41afe6 | |||
5a9d943a6c | |||
44fb246288 | |||
3cc7498e66 | |||
84ce883285 | |||
f8dd9df285 | |||
860cf6a658 | |||
a24a50ce44 | |||
fc9187297b | |||
eb26df874d | |||
27cbb1e57b | |||
66aa79f90f | |||
8b9a70b386 | |||
872a2d15e2 | |||
7085567a08 | |||
19b07a8a8c | |||
0ed78a4806 | |||
ca8fb4663f | |||
ec34ce481e | |||
b0c4089e26 | |||
b23148c71d | |||
c9a336a0a5 | |||
0744426c15 | |||
e6f4bd5531 | |||
ab6f4799c9 | |||
f4beecead3 | |||
17e6d56bb7 | |||
29881b73f9 | |||
8c30582b9a | |||
06de8abb44 | |||
a65ee65606 | |||
a6f49168b7 | |||
1d1e6121fa | |||
30b86499cd | |||
bb8daa81d2 | |||
763ce08902 | |||
a4e1055d86 | |||
ed89afd1bd | |||
13e26b4a1c | |||
3be56767a1 | |||
d184019a8e | |||
6df542a162 | |||
96ca1d1a37 |
@ -24,6 +24,7 @@ _jsp\.java$
|
||||
/classes/
|
||||
|
||||
# Android-specific ignores
|
||||
^lib/client/libs
|
||||
^routerjars/libs
|
||||
local.properties
|
||||
signing.properties
|
||||
@ -35,7 +36,6 @@ signing.properties
|
||||
.*.iws
|
||||
|
||||
#Gradle
|
||||
^.gradle
|
||||
build
|
||||
|
||||
# I2P-specific ignores
|
||||
|
12
.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
|
||||
lang_map = he: iw, id: in, pt_BR: pt-rBR, ru_RU: ru, sv_SE: sv, tr_TR: tr, uk_UA: uk, yi: ji, zh_CN: zh, zh_TW: zh-rTW
|
||||
|
||||
[I2P.android]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
minimum_perc = 50
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 50
|
||||
|
||||
[I2P.android_lib_client]
|
||||
file_filter = client/src/main/res/values-<lang>/strings.xml
|
||||
source_file = client/src/main/res/values/strings.xml
|
||||
[I2P.android_lib_helper]
|
||||
file_filter = lib/helper/src/main/res/values-<lang>/strings.xml
|
||||
minimum_perc = 50
|
||||
source_file = lib/helper/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 50
|
||||
|
||||
|
62
CHANGELOG
@ -1,4 +1,64 @@
|
||||
0.9.20
|
||||
0.9.33 / 2018-02-18
|
||||
* Translation updates
|
||||
|
||||
0.9.32 / 2017-11-28
|
||||
* Fixed "Application Not Responding" error when restarting all tunnels
|
||||
* Fixed crashes when:
|
||||
* opening the console menu
|
||||
* starting the router
|
||||
* starting the router for the first time
|
||||
* viewing tunnels
|
||||
* viewing the addressbook
|
||||
* opening the addressbook menu
|
||||
* configuring addressbook subscriptions
|
||||
* using a configuration wizard
|
||||
* loading a B64 Destination from file
|
||||
* viewing tunnel settings
|
||||
* saving tunnel settings
|
||||
* installing a tunnel's recommended app without a market app
|
||||
* installing a browser without a market app
|
||||
* rotating the screen while using the built-in browser
|
||||
* Added a "sync" icon to more clearly indicate tunnel "starting" status
|
||||
* Updated Firefox browser config instructions for Firefox Quantum
|
||||
* Translation updates
|
||||
|
||||
0.9.31 / 2017-08-19
|
||||
* Fixed various crashes in the Tunnels UI
|
||||
* Updated Firefox browser config instructions
|
||||
* Minor bug fixes
|
||||
* Dependency and translation updates
|
||||
|
||||
0.9.30 / 2017-05-20
|
||||
* Fixed crashes when creating or deleting tunnels, or adding names to the
|
||||
private addressbook
|
||||
* Minor bug fixes
|
||||
* Dependency and translation updates
|
||||
|
||||
0.9.29 / 2017-03-27
|
||||
* Dependency and translation updates
|
||||
|
||||
0.9.28 / 2017-01-02
|
||||
* Bug fixes and translation updates
|
||||
|
||||
0.9.27 / 2016-11-20 / 64ff68efe98c345acb6ba1d0432fa49d1d650358
|
||||
* Removed kytv's IRC server from default tunnel list
|
||||
* Translation updates
|
||||
|
||||
0.9.26 / 2016-06-13 / b003272c8b504bb0d904edca2e95359a57c9a52c
|
||||
* Fixed "I2CP already listening" bug
|
||||
* Fixed crash when adding tunnel to empty list
|
||||
* Translation updates
|
||||
|
||||
0.9.25 / 2016-04-17 / 46d45a878a2b73394b26ca27dbe6c696dedcf1c3
|
||||
* Fixed a bug on Samsung Android 4.2 devices
|
||||
* Dependency improvements
|
||||
* Translation updates
|
||||
|
||||
0.9.22 / 2015-10-10 / 0f73ef90b81e2cf3d55f0ea2b0a16e1f10da40ad
|
||||
* Updated browser config guide
|
||||
* Bug fixes and translation updates
|
||||
|
||||
0.9.20 / 2015-06-18 / 5fdaabeb5fa955caac90f1390adbdeaeae42fdf1
|
||||
* Simplified the main interface
|
||||
* Language can be configured
|
||||
* Tunnels can now be edited
|
||||
|
10
README.md
@ -48,6 +48,12 @@ systemProp.socksProxyPort=9150
|
||||
|
||||
2. Check out the [`i2p.i2p`](https://github.com/i2p/i2p.i2p) repository.
|
||||
|
||||
3. Create a `local.properties` file in `i2p.android.base/lib/client` containing:
|
||||
|
||||
```
|
||||
ndk.dir=/path/to/ndk
|
||||
```
|
||||
|
||||
3. Create a `local.properties` file in `i2p.android.base/routerjars` containing:
|
||||
|
||||
```
|
||||
@ -107,8 +113,8 @@ systemProp.socksProxyPort=9150
|
||||
signing.keyId=
|
||||
signing.password=
|
||||
signing.secretKeyRingFile=/path/to/secring.gpg
|
||||
ossrhUsername=
|
||||
ossrhPassword=
|
||||
NEXUS_USERNAME=
|
||||
NEXUS_PASSWORD=
|
||||
```
|
||||
|
||||
2. `gradle :client:uploadArchives`
|
||||
|
25
RELEASE-PROCESS.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Release Process
|
||||
|
||||
1. Check out a clean copy of i2p.i2p at the correct release version.
|
||||
2. Edit `routerjars/local.properties` to use the clean i2p.i2p copy.
|
||||
3. Pull the latest translations with `tx pull -a` and commit them. (If you don't have the `tx` command, do `pip install transifex-client` )
|
||||
4. Ensure that `signing.properties` contains the details of the release key.
|
||||
5. Edit `gradle.properties` to bump the I2P version.
|
||||
6. Edit `app/build.gradle` to bump the Android version number.
|
||||
7. If the helper has changed since the last release, edit
|
||||
`lib/helper/gradle.properties` to bump the version.
|
||||
8. `./gradlew clean assembleRelease`
|
||||
9. `./gradlew :lib:client:uploadArchives`
|
||||
10. If the helper version was changed: `./gradlew :lib:helper:uploadArchives`
|
||||
11. Check on Sonatype that everything worked, and close/release.
|
||||
12. Update local fdroidserver repo
|
||||
13. `cp app/build/outputs/apk/free/release/app-free-release.apk path/to/fdroid/repo/I2P-VERSION.apk`
|
||||
14. Update `path/to/fdroid/metadata/net.i2p.android.txt`
|
||||
15. `fdroid update`
|
||||
16. Push to download server and put in place.
|
||||
17. Check F-Droid repo works, and app works.
|
||||
18. `mtn ci gradle.properties lib/helper/gradle.properties app/build.gradle`
|
||||
19. Push free and donate builds to Google Play.
|
||||
20. Tag the new release. Example `mtn tag h: android-0.9.36`
|
||||
|
||||
|
25
TODO
@ -19,38 +19,61 @@
|
||||
- Style for addressbook headers
|
||||
- Change console FAM icon when possible
|
||||
<zzz> on the bottom right, the + and x icons might be better as a double-up arrow and double-down arrow?
|
||||
- Use Material design for LongPressButton
|
||||
- Highlight selected tunnel in two-pane mode
|
||||
|
||||
# Short-term
|
||||
|
||||
- Remove peers page (HTML version)
|
||||
- Add firewall help page showing current port settings
|
||||
- GMP 6
|
||||
- Fetch all JARs from Maven Central (ie. upload everything that I2P Android uses)
|
||||
- Disable uPnP when on cell networks
|
||||
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
|
||||
- Rewrite settings config handling
|
||||
- Rewrite InitActivities
|
||||
- I2PTunnel
|
||||
- Improve tunnel list status indicators
|
||||
- Icon overlay to indicate which tunnels are shared
|
||||
- Or reorder / group tunnels?
|
||||
- Show all messages somewhere
|
||||
- Bottom toolbar?
|
||||
- Icons/header images for tunnel types on details page
|
||||
- Setting to close when not on WiFi
|
||||
- Progress feedback for addressbook subscriptions reload
|
||||
- Display release notes directly on new router version
|
||||
- Fill out help pages
|
||||
- Fix navigation to specific settings pages
|
||||
- Rewrite release notes to be release-specific
|
||||
- Fix release notes UI, either make back button use clear or add buttons
|
||||
- Notify user when autostart fails?
|
||||
- NetDB tablet view fixes
|
||||
- Refresh detail fragment when changing tab
|
||||
- Move list to correct item when changing tab
|
||||
- Create nav history when viewing RI from LS
|
||||
- Handle NetDB null cases (failed lookup of requested hash in detail page)
|
||||
- Include GeoIP db for country info
|
||||
- Maybe change router-off mechanic for various pages? Enable as they become available?
|
||||
|
||||
# Medium-term
|
||||
|
||||
- SQLite naming service backend to store addresses more effectively
|
||||
- Leverage for name completion in e.g. browsers
|
||||
- Create/edit tunnels while router is not running
|
||||
- Separate out shared tunnel config
|
||||
- Convey to users that one config controls all shared tunnels
|
||||
- Network profiles
|
||||
- User selects profile in settings
|
||||
- Change network participation etc. based on profile
|
||||
- Also look at connection type: Connectivity.isConnectionFast()
|
||||
- Expose log level overrides
|
||||
- Bug report feature
|
||||
- Replace peers page (native version)
|
||||
- Improve graphs
|
||||
- Show fixed x range, not only available data
|
||||
- Think about pan/zoom
|
||||
- How to persist data across restarts?
|
||||
- Enable apps to specify when they don't need the router anymore
|
||||
|
||||
# Silent Store approval checks to confirm/implement
|
||||
|
||||
@ -98,6 +121,8 @@
|
||||
|
||||
# Long-term
|
||||
|
||||
- Reproducible builds
|
||||
- Extract RouterService into a library
|
||||
- Remote router support
|
||||
- Implement a "router wrapper" that can represent a local or remote router
|
||||
- Implement/use client APIs to talk to remote router
|
||||
|
@ -1,12 +1,10 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION as String)
|
||||
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION as String
|
||||
defaultConfig {
|
||||
versionCode 4745230
|
||||
versionName '0.9.20'
|
||||
versionCode 4745246
|
||||
versionName "$I2P_VERSION"
|
||||
minSdkVersion 9
|
||||
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION as String)
|
||||
|
||||
@ -19,10 +17,11 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
applicationIdSuffix '.debug'
|
||||
versionNameSuffix '-DEBUG'
|
||||
}
|
||||
@ -37,14 +36,18 @@ android {
|
||||
packagingOptions {
|
||||
exclude 'LICENSE.txt'
|
||||
}
|
||||
flavorDimensions 'tier'
|
||||
productFlavors {
|
||||
free {
|
||||
dimension 'tier'
|
||||
applicationId 'net.i2p.android'
|
||||
}
|
||||
donate {
|
||||
dimension 'tier'
|
||||
applicationId 'net.i2p.android.donate'
|
||||
}
|
||||
legacy {
|
||||
dimension 'tier'
|
||||
applicationId 'net.i2p.android.router'
|
||||
}
|
||||
}
|
||||
@ -52,45 +55,34 @@ android {
|
||||
|
||||
dependencies {
|
||||
// Local dependencies
|
||||
compile project(':routerjars')
|
||||
compile project(':client')
|
||||
implementation project(':lib:client')
|
||||
implementation project(':lib:helper')
|
||||
implementation project(path: ':routerjars', configuration: 'routerjars')
|
||||
|
||||
// Android Support Repository dependencies
|
||||
compile 'com.android.support:support-v4:22.2.0'
|
||||
compile 'com.android.support:appcompat-v7:22.2.0'
|
||||
compile 'com.android.support:recyclerview-v7:22.2.0'
|
||||
def supportVersion = '25.3.1'
|
||||
implementation "com.android.support:support-v4:$supportVersion"
|
||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||
implementation "com.android.support:preference-v7:$supportVersion"
|
||||
implementation "com.android.support:preference-v14:$supportVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$supportVersion"
|
||||
|
||||
// Remote dependencies
|
||||
compile 'net.i2p.android.ext:floatingactionbutton:1.9.0'
|
||||
compile files('libs/androidplot-core-0.6.1.jar')
|
||||
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
compile 'com.pnikosis:materialish-progress:1.5'
|
||||
compile 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
|
||||
compile ('com.mcxiaoke.viewpagerindicator:library:2.4.1') {
|
||||
exclude group: 'com.android.support', module: 'support-v4'
|
||||
}
|
||||
implementation 'com.androidplot:androidplot-core:1.4.1'
|
||||
implementation 'com.eowise:recyclerview-stickyheaders:0.5.2@aar'
|
||||
implementation 'com.inkapplications.viewpageindicator:library:2.4.4'
|
||||
implementation 'com.pnikosis:materialish-progress:1.7'
|
||||
implementation "net.i2p:router:$I2P_VERSION"
|
||||
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||
implementation 'org.sufficientlysecure:html-textview:3.1'
|
||||
|
||||
// Testing-only dependencies
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
|
||||
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:support-v4:7bb6e40a18774aa2595e4d8f9fe0ae14e61670f71a1279272fb0b79b8be71180',
|
||||
'com.android.support:appcompat-v7:2d5867698410b41f75140c91d6c1e58da74ae0f97baf6e0bdd1f7cc1017ceb2c',
|
||||
'com.android.support:recyclerview-v7:3a8da14585fa1c81f06e7cef4d93a7641f0323d8f984ff9a7bd7a6e416b46888',
|
||||
'net.i2p.android.ext:floatingactionbutton:b41eae5fe6be599e3fade00273521b0914f2e199d5f04c50fa34cfe935347f76',
|
||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
|
||||
'com.eowise:recyclerview-stickyheaders:7b236da49b33b840e9ba6e7e4182218d1a2d9047236fdbc3ca947352f9b0883b',
|
||||
'com.mcxiaoke.viewpagerindicator:library:1e8aad664137f68abdfee94889f6da3dc98be652a235176a403965a07a25de62',
|
||||
]
|
||||
}
|
||||
|
||||
project.ext.i2pbase = '../i2p.i2p'
|
||||
project.ext.i2pbase = "../i2p.i2p"
|
||||
def Properties props = new Properties()
|
||||
def propFile = new File(project(':routerjars').projectDir, 'local.properties')
|
||||
|
||||
|
29
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
-dontobfuscate
|
||||
-dontoptimize
|
||||
-dontpreverify
|
||||
-dontshrink
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
|
||||
# Workaround for Samsung Android 4.2 bug
|
||||
# https://code.google.com/p/android/issues/detail?id=78377
|
||||
# https://code.google.com/p/android/issues/detail?id=78377#c188
|
||||
# https://code.google.com/p/android/issues/detail?id=78377#c302
|
||||
-keepattributes **
|
||||
-keep class !android.support.v7.view.menu.**,** {*;}
|
||||
-dontwarn **
|
||||
-dontnote **
|
@ -16,6 +16,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.hasSibling;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
@ -57,13 +58,13 @@ public class I2PActivityTest extends ActivityInstrumentationTestCase2<I2PActivit
|
||||
public void testMainSwipe() {
|
||||
onView(withId(R.id.router_onoff_button)).check(matches(isDisplayed()));
|
||||
|
||||
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
|
||||
onView(allOf(withId(R.id.pager), withParent(hasSibling(withId(R.id.main_toolbar))))).perform(swipeLeft());
|
||||
onView(withId(R.id.router_onoff_button)).check(matches(not(isDisplayed())));
|
||||
onView(withText(R.string.label_i2ptunnel_client)).check(matches(isDisplayed()));
|
||||
|
||||
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
|
||||
onView(allOf(withId(R.id.pager), withParent(hasSibling(withId(R.id.main_toolbar))))).perform(swipeLeft());
|
||||
// TODO: test tunnels ViewPager
|
||||
onView(allOf(withId(R.id.pager), hasSibling(withId(R.id.main_toolbar)))).perform(swipeLeft());
|
||||
onView(allOf(withId(R.id.pager), withParent(hasSibling(withId(R.id.main_toolbar))))).perform(swipeLeft());
|
||||
onView(withText(R.string.label_i2ptunnel_client)).check(matches(not(isDisplayed())));
|
||||
onView(withText(R.string.label_router)).check(matches(isDisplayed()));
|
||||
// TODO: test addressbook ViewPager
|
||||
|
@ -3,6 +3,9 @@
|
||||
package="net.i2p.android.router"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-sdk xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:overrideLibrary="android.support.v14.preference" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
@ -51,6 +54,7 @@
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable_addressbook" />
|
||||
|
140
app/src/main/java/android/support/v4/view/CustomViewPager.java
Normal file
@ -0,0 +1,140 @@
|
||||
package android.support.v4.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.os.ParcelableCompat;
|
||||
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class CustomViewPager extends ViewPager {
|
||||
private boolean mEnabled;
|
||||
private int mFixedPage;
|
||||
private int mFixedPageString;
|
||||
|
||||
public CustomViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mEnabled = false;
|
||||
mFixedPage = -1;
|
||||
mFixedPageString = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return mEnabled && mFixedPage < 0 && super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
return mEnabled && mFixedPage < 0 && super.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentItem(int item) {
|
||||
if ((mEnabled && (mFixedPage < 0 || item == mFixedPage))
|
||||
|| (!mEnabled && item == 0))
|
||||
super.setCurrentItem(item);
|
||||
else if (!mEnabled)
|
||||
Toast.makeText(getContext(), Util.getRouterContext() == null ?
|
||||
R.string.router_not_running : R.string.router_shutting_down,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
else if (mFixedPageString > 0)
|
||||
Toast.makeText(getContext(), getContext().getString(mFixedPageString),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void setPagingEnabled(boolean enabled) {
|
||||
mEnabled = enabled;
|
||||
updatePagingState();
|
||||
}
|
||||
|
||||
public void setFixedPage(int page, int res) {
|
||||
mFixedPage = page;
|
||||
mFixedPageString = res;
|
||||
updatePagingState();
|
||||
}
|
||||
|
||||
public void updatePagingState() {
|
||||
if (mEnabled) {
|
||||
if (mFixedPage >= 0 && getCurrentItem() != mFixedPage)
|
||||
setCurrentItem(mFixedPage);
|
||||
|
||||
} else if (getCurrentItem() != 0)
|
||||
setCurrentItem(0);
|
||||
}
|
||||
|
||||
public static class SavedState extends ViewPager.SavedState {
|
||||
boolean enabled;
|
||||
int fixedPage;
|
||||
int fixedPageString;
|
||||
|
||||
public SavedState(Parcelable superState) {
|
||||
super(superState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeInt(enabled ? 1 : 0);
|
||||
out.writeInt(fixedPage);
|
||||
out.writeInt(fixedPageString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomViewPager.SavedState{"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " enabled=" + enabled + " fixedPage=" + fixedPage + "}";
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<SavedState> CREATOR
|
||||
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
|
||||
@Override
|
||||
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
|
||||
return new SavedState(in, loader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedState[] newArray(int size) {
|
||||
return new SavedState[size];
|
||||
}
|
||||
});
|
||||
|
||||
SavedState(Parcel in, ClassLoader loader) {
|
||||
super(in, loader);
|
||||
enabled = in.readInt() != 0;
|
||||
fixedPage = in.readInt();
|
||||
fixedPageString = in.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
Parcelable superState = super.onSaveInstanceState();
|
||||
SavedState ss = new SavedState(superState);
|
||||
ss.enabled = mEnabled;
|
||||
ss.fixedPage = mFixedPage;
|
||||
ss.fixedPageString = mFixedPageString;
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (!(state instanceof SavedState)) {
|
||||
super.onRestoreInstanceState(state);
|
||||
return;
|
||||
}
|
||||
|
||||
SavedState ss = (SavedState)state;
|
||||
super.onRestoreInstanceState(ss.getSuperState());
|
||||
|
||||
mEnabled = ss.enabled;
|
||||
mFixedPage = ss.fixedPage;
|
||||
mFixedPageString = ss.fixedPageString;
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/*
|
||||
* The following code was written by Matthew Wiggins
|
||||
* and is released under the APACHE 2.0 license
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Slight modifications and bugfixes by Sponge <sponge@mail.i2p>
|
||||
* These modifications are released under the WTFPL (any version)
|
||||
*
|
||||
* We don't need negative numbers yet, and may never need to.
|
||||
*
|
||||
* XML Usage example:
|
||||
*
|
||||
* <com.hlidskialf.android.preference.SeekBarPreference android:key="duration"
|
||||
* android:title="Duration of something"
|
||||
* android:summary="How long something will last"
|
||||
* android:dialogMessage="Something duration"
|
||||
* android:defaultValue="5"
|
||||
* android:text=" minutes"
|
||||
* android:max="60"
|
||||
* />
|
||||
*
|
||||
*/
|
||||
package com.hlidskialf.android.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
|
||||
|
||||
private static final String androidns = "http://schemas.android.com/apk/res/android";
|
||||
private SeekBar mSeekBar;
|
||||
private TextView mSplashText;
|
||||
private TextView mValueText;
|
||||
private Context mContext;
|
||||
private String mDialogMessage, mSuffix;
|
||||
private String mDefault = "0";
|
||||
private int mMax = 0;
|
||||
private int mValue = 0;
|
||||
private int mDirection = LinearLayout.HORIZONTAL;
|
||||
|
||||
|
||||
public SeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
int dialogMessageR = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
|
||||
mDialogMessage = (dialogMessageR == 0)
|
||||
? attrs.getAttributeValue(androidns, "dialogMessage")
|
||||
: context.getResources().getString(dialogMessageR);
|
||||
int textR = attrs.getAttributeResourceValue(androidns, "text", 0);
|
||||
mSuffix = (textR == 0)
|
||||
? attrs.getAttributeValue(androidns, "text")
|
||||
: context.getResources().getString(textR);
|
||||
mDefault = attrs.getAttributeValue(androidns, "defaultValue");
|
||||
mMax = Integer.parseInt(attrs.getAttributeValue(androidns, "max"));
|
||||
if (attrs.getAttributeValue(androidns, "direction") != null) {
|
||||
mDirection = Integer.parseInt(attrs.getAttributeValue(androidns, "direction"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView() {
|
||||
LinearLayout.LayoutParams params;
|
||||
LinearLayout layout = new LinearLayout(mContext);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(6, 6, 6, 10);
|
||||
|
||||
// Set the width so that it is as usable as possible.
|
||||
// We multiplymMax so that the smaller ranges will get a bigger area.
|
||||
|
||||
if (mDirection == LinearLayout.HORIZONTAL) {
|
||||
layout.setMinimumWidth(mMax*5);
|
||||
} else {
|
||||
layout.setMinimumHeight(mMax*5);
|
||||
}
|
||||
|
||||
mSplashText = new TextView(mContext);
|
||||
if (mDialogMessage != null) {
|
||||
mSplashText.setText(mDialogMessage);
|
||||
}
|
||||
layout.addView(mSplashText);
|
||||
|
||||
mValueText = new TextView(mContext);
|
||||
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||
mValueText.setTextSize(32);
|
||||
params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
layout.addView(mValueText, params);
|
||||
|
||||
mSeekBar = new SeekBar(mContext);
|
||||
mSeekBar.setOnSeekBarChangeListener(this);
|
||||
// Move the bar away from the changing text, so you can see it, and
|
||||
// move it away from the edges to improve usability for the end-ranges.
|
||||
mSeekBar.setPadding(6, 30, 6, 6);
|
||||
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
if (shouldPersist()) {
|
||||
mValue = Integer.parseInt(getPersistedString(mDefault));
|
||||
}
|
||||
mSeekBar.setMax(mMax);
|
||||
mSeekBar.setProgress(mValue);
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View v) {
|
||||
super.onBindDialogView(v);
|
||||
mSeekBar.setMax(mMax);
|
||||
mSeekBar.setProgress(mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restore, Object defaultValue) {
|
||||
super.onSetInitialValue(restore, defaultValue);
|
||||
if (restore) {
|
||||
mValue = shouldPersist() ? Integer.parseInt(getPersistedString(mDefault)) : 0;
|
||||
} else {
|
||||
mValue = (Integer) defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
|
||||
String t = String.valueOf(value);
|
||||
mValueText.setText(mSuffix == null ? t : t.concat(mSuffix));
|
||||
if (shouldPersist()) {
|
||||
persistString(t);
|
||||
}
|
||||
callChangeListener(value);
|
||||
}
|
||||
|
||||
public void onStartTrackingTouch(SeekBar seek) {
|
||||
}
|
||||
|
||||
public void onStopTrackingTouch(SeekBar seek) {
|
||||
}
|
||||
|
||||
public void setMax(int max) {
|
||||
mMax = max;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return mMax;
|
||||
}
|
||||
|
||||
public void setProgress(int progress) {
|
||||
mValue = progress;
|
||||
if (mSeekBar != null) {
|
||||
mSeekBar.setProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
return mValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
package com.pavelsikun.seekbarpreference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
/**
|
||||
* Based on MaterialSeekBarController created by mrbimc on 30.09.15.
|
||||
*/
|
||||
public class MaterialSeekBarController implements SeekBar.OnSeekBarChangeListener {
|
||||
|
||||
private final String TAG = getClass().getName();
|
||||
|
||||
public static final int DEFAULT_CURRENT_VALUE = 50;
|
||||
private static final int DEFAULT_MAX_VALUE = 100;
|
||||
private static final String DEFAULT_MEASUREMENT_UNIT = "";
|
||||
|
||||
private int mMaxValue;
|
||||
private int mMaxDigits;
|
||||
private int mCurrentValue;
|
||||
private String mMeasurementUnit;
|
||||
|
||||
private SeekBar mSeekBar;
|
||||
private TextView mSeekBarValue;
|
||||
private TextView mMeasurementUnitView;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private Persistable mPersistable;
|
||||
|
||||
public MaterialSeekBarController(Context context, AttributeSet attrs, Persistable persistable) {
|
||||
mContext = context;
|
||||
mPersistable = persistable;
|
||||
init(attrs, null);
|
||||
}
|
||||
|
||||
private void init(AttributeSet attrs, View view) {
|
||||
setValuesFromXml(attrs);
|
||||
if(view != null) onBindView(view);
|
||||
}
|
||||
private void setValuesFromXml(@Nullable AttributeSet attrs) {
|
||||
if (attrs == null) {
|
||||
mCurrentValue = DEFAULT_CURRENT_VALUE;
|
||||
mMaxValue = DEFAULT_MAX_VALUE;
|
||||
mMeasurementUnit = DEFAULT_MEASUREMENT_UNIT;
|
||||
} else {
|
||||
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SeekBarPreference);
|
||||
try {
|
||||
mMaxValue = a.getInt(R.styleable.SeekBarPreference_msbp_maxValue, DEFAULT_MAX_VALUE);
|
||||
mCurrentValue = a.getInt(R.styleable.SeekBarPreference_msbp_defaultValue, DEFAULT_CURRENT_VALUE);
|
||||
|
||||
if(mCurrentValue > mMaxValue) mCurrentValue = mMaxValue / 2;
|
||||
mMeasurementUnit = a.getString(R.styleable.SeekBarPreference_msbp_measurementUnit);
|
||||
if (mMeasurementUnit == null)
|
||||
mMeasurementUnit = DEFAULT_MEASUREMENT_UNIT;
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
mMaxDigits = (int) Math.log10(mMaxValue) + 1;
|
||||
}
|
||||
|
||||
public void onBindView(@NonNull View view) {
|
||||
|
||||
mSeekBar = (SeekBar) view.findViewById(R.id.seekbar);
|
||||
mSeekBar.setMax(mMaxValue);
|
||||
mSeekBar.setOnSeekBarChangeListener(this);
|
||||
|
||||
mSeekBarValue = (TextView) view.findViewById(R.id.seekbar_value);
|
||||
setPaddedValue(mCurrentValue);
|
||||
|
||||
mMeasurementUnitView = (TextView) view.findViewById(R.id.measurement_unit);
|
||||
mMeasurementUnitView.setText(mMeasurementUnit);
|
||||
|
||||
mSeekBar.setProgress(mCurrentValue);
|
||||
|
||||
if (!view.isEnabled()) {
|
||||
mSeekBar.setEnabled(false);
|
||||
mSeekBarValue.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onSetInitialValue(boolean restoreValue, @NonNull Object defaultValue) {
|
||||
mCurrentValue = mMaxValue / 2;
|
||||
try {
|
||||
mCurrentValue = (Integer) defaultValue;
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Invalid default value: " + defaultValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (mSeekBar != null) mSeekBar.setEnabled(enabled);
|
||||
if (mSeekBarValue != null) mSeekBarValue.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
|
||||
if (mSeekBar != null) mSeekBar.setEnabled(!disableDependent);
|
||||
if (mSeekBarValue != null) mSeekBarValue.setEnabled(!disableDependent);
|
||||
}
|
||||
|
||||
//SeekBarListener:
|
||||
@Override
|
||||
public void onProgressChanged(@NonNull SeekBar seekBar, int progress, boolean fromUser) {
|
||||
mCurrentValue = progress;
|
||||
setPaddedValue(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(@NonNull SeekBar seekBar) {
|
||||
setCurrentValue(mCurrentValue);
|
||||
}
|
||||
|
||||
private void setPaddedValue(int value) {
|
||||
mSeekBarValue.setText(String.format("%0" + mMaxDigits +"d", value));
|
||||
}
|
||||
|
||||
|
||||
//public methods for manipulating this widget from java:
|
||||
public void setCurrentValue(int value) {
|
||||
mCurrentValue = value;
|
||||
if (mPersistable != null) mPersistable.onPersist(value);
|
||||
}
|
||||
|
||||
public int getCurrentValue() {
|
||||
return mCurrentValue;
|
||||
}
|
||||
|
||||
|
||||
public void setMaxValue(int maxValue) {
|
||||
mMaxValue = maxValue;
|
||||
if (mSeekBar != null) mSeekBar.setMax(mMaxValue);
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
|
||||
public void setMeasurementUnit(String measurementUnit) {
|
||||
mMeasurementUnit = measurementUnit;
|
||||
if (mMeasurementUnitView != null) mMeasurementUnitView.setText(mMeasurementUnit);
|
||||
}
|
||||
|
||||
public String getMeasurementUnit() {
|
||||
return mMeasurementUnit;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.pavelsikun.seekbarpreference;
|
||||
|
||||
/**
|
||||
* Created by mrbimc on 04.10.15.
|
||||
*/
|
||||
public interface Persistable {
|
||||
void onPersist(int value);
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package com.pavelsikun.seekbarpreference;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class SeekBarPreference extends Preference implements Persistable {
|
||||
|
||||
private MaterialSeekBarController mController;
|
||||
|
||||
public SeekBarPreference(Context context) {
|
||||
super(context);
|
||||
init(null);
|
||||
}
|
||||
|
||||
public SeekBarPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
private void init(AttributeSet attrs) {
|
||||
setLayoutResource(R.layout.seekbar_preference);
|
||||
mController = new MaterialSeekBarController(getContext(), attrs, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PreferenceViewHolder viewHolder) {
|
||||
super.onBindViewHolder(viewHolder);
|
||||
mController.onBindView(viewHolder.itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(@NonNull TypedArray ta, int index) {
|
||||
if(mController != null) return ta.getInt(index, mController.getCurrentValue());
|
||||
else return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, @NonNull Object defaultValue) {
|
||||
int average = mController.getMaxValue() / 2;
|
||||
if(restoreValue) mController.setCurrentValue(getPersistedInt(average));
|
||||
else mController.onSetInitialValue(restoreValue, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
mController.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
|
||||
super.onDependencyChanged(dependency, disableDependent);
|
||||
mController.onDependencyChanged(dependency, disableDependent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPersist(int value) {
|
||||
persistInt(value);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public String getMeasurementUnit() {
|
||||
return mController.getMeasurementUnit();
|
||||
}
|
||||
|
||||
public void setMeasurementUnit(String measurementUnit) {
|
||||
mController.setMeasurementUnit(measurementUnit);
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return mController.getMaxValue();
|
||||
}
|
||||
|
||||
public void setMaxValue(int maxValue) {
|
||||
mController.setMaxValue(maxValue);
|
||||
}
|
||||
|
||||
public int getCurrentValue() {
|
||||
return mController.getCurrentValue();
|
||||
}
|
||||
|
||||
public void setCurrentValue(int value) {
|
||||
mController.setCurrentValue(value);
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ import net.i2p.android.router.service.State;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.android.util.MemoryFragmentPagerAdapter;
|
||||
import net.i2p.android.widget.CustomViewPager;
|
||||
import android.support.v4.view.CustomViewPager;
|
||||
import net.i2p.android.widget.SlidingTabLayout;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@ -147,10 +147,16 @@ public class I2PActivity extends I2PActivityBase implements
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
if (action.equals("net.i2p.android.router.START_I2P")) {
|
||||
if (mViewPager.getCurrentItem() != 0)
|
||||
mViewPager.setCurrentItem(0, false);
|
||||
autoStart();
|
||||
switch (action) {
|
||||
case "net.i2p.android.router.START_I2P":
|
||||
if (mViewPager.getCurrentItem() != 0)
|
||||
mViewPager.setCurrentItem(0, false);
|
||||
autoStart();
|
||||
break;
|
||||
|
||||
case Intent.ACTION_PICK:
|
||||
mViewPager.setFixedPage(2, R.string.select_an_address);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,12 @@ import net.i2p.router.RouterContext;
|
||||
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.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.ReusableGZIPInputStream;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.RFC822Date;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.ColorMatrix;
|
||||
@ -11,6 +12,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
@ -84,7 +86,11 @@ public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHold
|
||||
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
||||
Uri uri = Uri.parse(uriMarket);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
mCtx.startActivity(intent);
|
||||
try {
|
||||
mCtx.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(mCtx, R.string.no_market_app, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
@ -18,14 +22,17 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.android.util.FragmentUtils;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
import java.util.List;
|
||||
@ -37,6 +44,7 @@ public class TunnelDetailFragment extends Fragment {
|
||||
private TunnelControllerGroup mGroup;
|
||||
private TunnelEntry mTunnel;
|
||||
private Toolbar mToolbar;
|
||||
private ImageView mStatus;
|
||||
|
||||
public static TunnelDetailFragment newInstance(int tunnelId) {
|
||||
TunnelDetailFragment f = new TunnelDetailFragment();
|
||||
@ -69,21 +77,33 @@ public class TunnelDetailFragment extends Fragment {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String error;
|
||||
List<TunnelController> controllers;
|
||||
try {
|
||||
mGroup = TunnelControllerGroup.getInstance();
|
||||
error = mGroup == null ? getResources().getString(R.string.i2ptunnel_not_initialized) : null;
|
||||
controllers = mGroup.getControllers();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
mGroup = null;
|
||||
controllers = null;
|
||||
error = iae.toString();
|
||||
}
|
||||
|
||||
if (mGroup == null) {
|
||||
// Show error
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
error, Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
} else if (getArguments().containsKey(TUNNEL_ID)) {
|
||||
int tunnelId = getArguments().getInt(TUNNEL_ID);
|
||||
mTunnel = new TunnelEntry(getActivity(),
|
||||
mGroup.getControllers().get(tunnelId),
|
||||
tunnelId);
|
||||
try {
|
||||
TunnelController controller = controllers.get(tunnelId);
|
||||
mTunnel = new TunnelEntry(getActivity(), controller, tunnelId);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// Tunnel doesn't exist
|
||||
Util.e("Could not load tunnel details", e);
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +123,10 @@ public class TunnelDetailFragment extends Fragment {
|
||||
updateToolbar();
|
||||
|
||||
if (mTunnel != null) {
|
||||
mStatus = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||
updateStatus();
|
||||
ViewCompat.setTransitionName(mStatus, "status" + mTunnel.getId());
|
||||
|
||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||
name.setText(mTunnel.getName());
|
||||
|
||||
@ -112,8 +136,26 @@ public class TunnelDetailFragment extends Fragment {
|
||||
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
|
||||
description.setText(mTunnel.getDescription());
|
||||
|
||||
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
||||
details.setText(mTunnel.getDetails());
|
||||
if (!mTunnel.getDetails().isEmpty()) {
|
||||
v.findViewById(R.id.tunnel_details_container).setVisibility(View.VISIBLE);
|
||||
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
||||
View copyDetails = v.findViewById(R.id.tunnel_details_copy);
|
||||
details.setText(mTunnel.getDetails());
|
||||
if (!mTunnel.isClient()) {
|
||||
copyDetails.setVisibility(View.VISIBLE);
|
||||
copyDetails.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
|
||||
copyToClipbardLegacy();
|
||||
else
|
||||
copyToClipboardHoneycomb();
|
||||
|
||||
Toast.makeText(getActivity(), R.string.address_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
View accessIfacePortItem = v.findViewById(R.id.tunnel_access_interface_port_item);
|
||||
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
|
||||
@ -178,11 +220,17 @@ public class TunnelDetailFragment extends Fragment {
|
||||
Uri uri = mTunnel.getRecommendedAppForTunnel();
|
||||
if (uri != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
startActivity(intent);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getContext(),
|
||||
R.string.no_market_app,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
|
||||
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
}
|
||||
});
|
||||
@ -218,6 +266,14 @@ public class TunnelDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
mStatus.setImageDrawable(mTunnel.getStatusIcon());
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||
mStatus.setBackgroundDrawable(mTunnel.getStatusBackground());
|
||||
else
|
||||
mStatus.setBackground(mTunnel.getStatusBackground());
|
||||
}
|
||||
|
||||
private boolean onToolbarItemSelected(MenuItem item) {
|
||||
if (mTunnel == null)
|
||||
return false;
|
||||
@ -231,6 +287,8 @@ public class TunnelDetailFragment extends Fragment {
|
||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||
// Reload the toolbar to change the start/stop action
|
||||
updateToolbar();
|
||||
// Update the status icon
|
||||
updateStatus();
|
||||
return true;
|
||||
case R.id.action_stop_tunnel:
|
||||
mTunnel.getController().stopTunnel();
|
||||
@ -239,39 +297,84 @@ public class TunnelDetailFragment extends Fragment {
|
||||
+ ' ' + mTunnel.getName(), Toast.LENGTH_LONG).show();
|
||||
// Reload the toolbar to change the start/stop action
|
||||
updateToolbar();
|
||||
// Update the status icon
|
||||
updateStatus();
|
||||
return true;
|
||||
case R.id.action_edit_tunnel:
|
||||
mCallback.onEditTunnel(mTunnel.getId());
|
||||
return true;
|
||||
case R.id.action_delete_tunnel:
|
||||
DialogFragment dg = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.i2ptunnel_delete_confirm_message)
|
||||
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
List<String> msgs = TunnelUtil.deleteTunnel(
|
||||
I2PAppContext.getGlobalContext(),
|
||||
mGroup, mTunnel.getId(), null);
|
||||
dialog.dismiss();
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||
mCallback.onTunnelDeleted(mTunnel.getId(),
|
||||
mGroup.getControllers().size());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
};
|
||||
dg.show(getFragmentManager(), "delete_tunnel_dialog");
|
||||
DialogFragment dg = DeleteTunnelDialogFragment.newInstance();
|
||||
dg.show(getChildFragmentManager(), "delete_tunnel_dialog");
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDeleteTunnel() {
|
||||
List<String> msgs = TunnelUtil.deleteTunnel(
|
||||
I2PAppContext.getGlobalContext(),
|
||||
mGroup, mTunnel.getId(), null);
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||
mCallback.onTunnelDeleted(mTunnel.getId(),
|
||||
mGroup.getControllers().size());
|
||||
}
|
||||
|
||||
private void copyToClipbardLegacy() {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(mTunnel.getDetails());
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
private void copyToClipboardHoneycomb() {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText(
|
||||
mTunnel.getName(), mTunnel.getDetails());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
public static class DeleteTunnelDialogFragment extends DialogFragment {
|
||||
TunnelDetailFragment mListener;
|
||||
|
||||
public static DialogFragment newInstance() {
|
||||
return new DeleteTunnelDialogFragment();
|
||||
}
|
||||
|
||||
private void onAttachToParentFragment(Fragment fragment) {
|
||||
// Verify that the host fragment implements the callback interface
|
||||
try {
|
||||
// Instantiate the TunnelDetailFragment so we can send events to the host
|
||||
mListener = (TunnelDetailFragment) fragment;
|
||||
} catch (ClassCastException e) {
|
||||
// The fragment doesn't implement the interface, throw exception
|
||||
throw new ClassCastException(fragment.toString()
|
||||
+ " must be TunnelDetailFragment");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
onAttachToParentFragment(getParentFragment());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.i2ptunnel_delete_confirm_message)
|
||||
.setPositiveButton(R.string.i2ptunnel_delete_confirm_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
mListener.onDeleteTunnel();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,19 @@ import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class TunnelEntry {
|
||||
public static final int RUNNING = 1;
|
||||
@ -26,18 +30,32 @@ public class TunnelEntry {
|
||||
private final TunnelController mController;
|
||||
private final int mId;
|
||||
|
||||
/**
|
||||
* @return the new TunnelEntry, or null if there was an error.
|
||||
*/
|
||||
public static TunnelEntry createNewTunnel(
|
||||
Context ctx,
|
||||
TunnelControllerGroup tcg,
|
||||
TunnelConfig cfg) {
|
||||
int tunnelId = tcg.getControllers().size();
|
||||
List<String> msgs = TunnelUtil.saveTunnel(
|
||||
I2PAppContext.getGlobalContext(), tcg, -1, cfg);
|
||||
TunnelEntry ret = null;
|
||||
List<String> msgs = new ArrayList<>();
|
||||
SaveTunnelTask task = new SaveTunnelTask(tcg, -1, cfg);
|
||||
try {
|
||||
msgs.addAll(task.execute().get());
|
||||
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
||||
ret = new TunnelEntry(ctx, cur, tunnelId);
|
||||
} catch (InterruptedException e) {
|
||||
Util.e("Interrupted while saving tunnel config", e);
|
||||
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
|
||||
} catch (ExecutionException e) {
|
||||
Util.e("Error while saving tunnel config", e);
|
||||
msgs.add(ctx.getString(R.string.i2ptunnel_msg_config_save_failed));
|
||||
}
|
||||
// TODO: Do something else with the other messages.
|
||||
Toast.makeText(ctx.getApplicationContext(),
|
||||
msgs.get(0), Toast.LENGTH_LONG).show();
|
||||
TunnelController cur = TunnelUtil.getController(tcg, tunnelId);
|
||||
return new TunnelEntry(ctx, cur, tunnelId);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public TunnelEntry(Context context, TunnelController controller, int id) {
|
||||
@ -252,7 +270,7 @@ public class TunnelEntry {
|
||||
if (isClient())
|
||||
details = getClientDestination();
|
||||
else
|
||||
details = "";
|
||||
details = getDestHashBase32();
|
||||
return details;
|
||||
}
|
||||
|
||||
@ -262,6 +280,8 @@ public class TunnelEntry {
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
||||
case STARTING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.ic_sync_black_24dp);
|
||||
case RUNNING:
|
||||
case NOT_RUNNING:
|
||||
default:
|
||||
|
@ -3,6 +3,7 @@ package net.i2p.android.i2ptunnel;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -64,8 +65,13 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
||||
}
|
||||
|
||||
public void addTunnel(TunnelEntry tunnel) {
|
||||
boolean wasEmpty = mTunnels.isEmpty();
|
||||
mTunnels.add(tunnel);
|
||||
notifyItemInserted(mTunnels.size()-1);
|
||||
if (wasEmpty) {
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
notifyItemInserted(mTunnels.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public TunnelEntry getTunnel(int position) {
|
||||
@ -138,6 +144,8 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
||||
tvh.status.setBackgroundDrawable(tunnel.getStatusBackground());
|
||||
else
|
||||
tvh.status.setBackground(tunnel.getStatusBackground());
|
||||
ViewCompat.setTransitionName(tvh.status,
|
||||
"status" + tunnel.getId());
|
||||
|
||||
tvh.name.setText(tunnel.getName());
|
||||
tvh.description.setText(tunnel.getDescription());
|
||||
@ -151,9 +159,11 @@ public class TunnelEntryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
|
||||
mActivatedPosition = position;
|
||||
notifyItemChanged(oldPosition);
|
||||
notifyItemChanged(position);
|
||||
mListener.onTunnelSelected(tunnel.getId(),
|
||||
Pair.create((View)tvh.name, mCtx.getString(R.string.TUNNEL_NAME)),
|
||||
Pair.create((View)tvh.description, mCtx.getString(R.string.TUNNEL_DESCRIPTION)));
|
||||
Pair<View, String> statusPair = Pair.create(
|
||||
(View)tvh.status,
|
||||
ViewCompat.getTransitionName(tvh.status));
|
||||
Pair<View, String>[] pairs = new Pair[]{ statusPair};
|
||||
mListener.onTunnelSelected(tunnel.getId(), pairs);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -53,8 +53,7 @@ public class TunnelListFragment extends Fragment implements
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnTunnelSelectedListener {
|
||||
void onTunnelSelected(int tunnelId, Pair<View, String> tunnelName,
|
||||
Pair<View, String> tunnelDescription);
|
||||
void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs);
|
||||
}
|
||||
|
||||
public static TunnelListFragment newInstance(boolean showClientTunnels) {
|
||||
@ -151,14 +150,21 @@ public class TunnelListFragment extends Fragment implements
|
||||
};
|
||||
|
||||
public void updateState(State state) {
|
||||
if (state == State.STOPPING || state == State.STOPPED ||
|
||||
state == State.MANUAL_STOPPING ||
|
||||
state == State.MANUAL_STOPPED ||
|
||||
state == State.MANUAL_QUITTING ||
|
||||
state == State.MANUAL_QUITTED)
|
||||
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
|
||||
else
|
||||
initTunnels();
|
||||
try {
|
||||
if (state == State.INIT ||
|
||||
state == State.STARTING || // Wait until RouterContext is initialised
|
||||
state == State.STOPPING ||
|
||||
state == State.STOPPED ||
|
||||
state == State.MANUAL_STOPPING ||
|
||||
state == State.MANUAL_STOPPED ||
|
||||
state == State.MANUAL_QUITTING ||
|
||||
state == State.MANUAL_QUITTED)
|
||||
getLoaderManager().destroyLoader(mClientTunnels ? CLIENT_LOADER_ID : SERVER_LOADER_ID);
|
||||
else
|
||||
initTunnels();
|
||||
} catch (IllegalStateException ise) {
|
||||
// Fragment isn't attached to any activity, so ignore state change
|
||||
}
|
||||
}
|
||||
|
||||
private void initTunnels() {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,14 @@ public class TunnelsContainer extends Fragment implements
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
private boolean showActions() {
|
||||
RouterContext rCtx = Util.getRouterContext();
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
return rCtx != null && tcg != null &&
|
||||
(tcg.getState() == ClientAppState.STARTING ||
|
||||
tcg.getState() == ClientAppState.RUNNING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.container_tunnels, container, false);
|
||||
@ -72,6 +80,7 @@ public class TunnelsContainer extends Fragment implements
|
||||
mViewPager = (ViewPager) v.findViewById(R.id.pager);
|
||||
mPageIndicator = (TitlePageIndicator) v.findViewById(R.id.page_indicator);
|
||||
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||
mNewTunnel.setVisibility(showActions() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (v.findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
@ -154,17 +163,16 @@ public class TunnelsContainer extends Fragment implements
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
RouterContext rCtx = Util.getRouterContext();
|
||||
TunnelControllerGroup tcg = TunnelControllerGroup.getInstance();
|
||||
boolean showActions = rCtx != null && tcg != null &&
|
||||
(tcg.getState() == ClientAppState.STARTING ||
|
||||
tcg.getState() == ClientAppState.RUNNING);
|
||||
boolean showActions = showActions();
|
||||
|
||||
menu.findItem(R.id.action_start_all_tunnels).setVisible(showActions);
|
||||
menu.findItem(R.id.action_stop_all_tunnels).setVisible(showActions);
|
||||
menu.findItem(R.id.action_restart_all_tunnels).setVisible(showActions);
|
||||
|
||||
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
|
||||
// Was causing a NPE in version 4745238 (0.9.31)
|
||||
if (mNewTunnel != null) {
|
||||
mNewTunnel.setVisibility(showActions ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -183,7 +191,10 @@ public class TunnelsContainer extends Fragment implements
|
||||
msgs = tcg.stopAllControllers();
|
||||
break;
|
||||
case R.id.action_restart_all_tunnels:
|
||||
msgs = tcg.restartAllControllers();
|
||||
// Do a manual stop-start cycle, because tcg.restartAllControllers() happens in the
|
||||
// foreground, whereas tcg.startAllControllers() fires off threads for starting.
|
||||
msgs = tcg.stopAllControllers();
|
||||
msgs.addAll(tcg.startAllControllers());
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
@ -205,10 +216,12 @@ public class TunnelsContainer extends Fragment implements
|
||||
TunnelConfig cfg = TunnelUtil.createConfigFromWizard(getActivity(), tcg, tunnelData);
|
||||
TunnelEntry tunnel = TunnelEntry.createNewTunnel(getActivity(), tcg, cfg);
|
||||
|
||||
if (tunnel.isClient() && mClientFrag != null)
|
||||
mClientFrag.addTunnel(tunnel);
|
||||
else if (mServerFrag != null)
|
||||
mServerFrag.addTunnel(tunnel);
|
||||
if (tunnel != null) {
|
||||
if (tunnel.isClient() && mClientFrag != null)
|
||||
mClientFrag.addTunnel(tunnel);
|
||||
else if (mServerFrag != null)
|
||||
mServerFrag.addTunnel(tunnel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -239,8 +252,7 @@ public class TunnelsContainer extends Fragment implements
|
||||
|
||||
// TunnelListFragment.OnTunnelSelectedListener
|
||||
|
||||
public final void onTunnelSelected(int tunnelId, Pair<View, String> tunnelName,
|
||||
Pair<View, String> tunnelDescription) {
|
||||
public final void onTunnelSelected(int tunnelId, Pair<View, String>[] pairs) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
@ -255,7 +267,7 @@ public class TunnelsContainer extends Fragment implements
|
||||
detailIntent.putExtra(TunnelDetailFragment.TUNNEL_ID, tunnelId);
|
||||
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
getActivity(), tunnelName, tunnelDescription);
|
||||
getActivity(), pairs);
|
||||
ActivityCompat.startActivity(getActivity(), detailIntent, options.toBundle());
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ package net.i2p.android.i2ptunnel.preferences;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
|
||||
import net.i2p.android.i2ptunnel.util.TunnelLogic;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
|
@ -2,27 +2,29 @@ package net.i2p.android.i2ptunnel.preferences;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceGroup;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.i2ptunnel.util.SaveTunnelTask;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.preferences.util.CustomPreferenceFragment;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.i2ptunnel.ui.TunnelConfig;
|
||||
|
||||
public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class BaseTunnelPreferenceFragment extends CustomPreferenceFragment {
|
||||
protected static final String ARG_TUNNEL_ID = "tunnelId";
|
||||
|
||||
protected TunnelControllerGroup mGroup;
|
||||
protected int mTunnelId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
String error;
|
||||
try {
|
||||
mGroup = TunnelControllerGroup.getInstance();
|
||||
@ -33,13 +35,30 @@ public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
|
||||
if (mGroup == null) {
|
||||
// TODO Show error
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
error, Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
} else if (getArguments().containsKey(ARG_TUNNEL_ID)) {
|
||||
mTunnelId = getArguments().getInt(ARG_TUNNEL_ID, 0);
|
||||
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
|
||||
try {
|
||||
TunnelUtil.writeTunnelToPreferences(getActivity(), mGroup, mTunnelId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Tunnel doesn't exist, or the tunnel config file could not be read
|
||||
Util.e("Could not load tunnel details", e);
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
R.string.i2ptunnel_no_tunnel_details, Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
}
|
||||
// https://stackoverflow.com/questions/17880437/which-settings-file-does-preferencefragment-read-write
|
||||
getPreferenceManager().setSharedPreferencesName(TunnelUtil.getPreferencesFilename(mTunnelId));
|
||||
loadPreferences();
|
||||
try {
|
||||
loadPreferences();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// mGroup couldn't load its config file
|
||||
Toast.makeText(getActivity().getApplicationContext(),
|
||||
iae.toString(), Toast.LENGTH_LONG).show();
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +83,17 @@ public abstract class BaseTunnelPreferenceFragment extends PreferenceFragment {
|
||||
private void saveTunnel() {
|
||||
if (mGroup != null) {
|
||||
TunnelConfig cfg = TunnelUtil.createConfigFromPreferences(getActivity(), mGroup, mTunnelId);
|
||||
TunnelUtil.saveTunnel(I2PAppContext.getGlobalContext(), mGroup, mTunnelId, cfg);
|
||||
SaveTunnelTask task = new SaveTunnelTask(mGroup, mTunnelId, cfg);
|
||||
try {
|
||||
// TODO: There used to be a possible ANR here, because the underlying I2P code
|
||||
// checks if the session is open as part of updating its config. We may need to save
|
||||
// completely asynchronously (and ensure we do actually save before the app closes).
|
||||
task.execute().get();
|
||||
} catch (InterruptedException e) {
|
||||
Util.e("Interrupted while saving tunnel config", e);
|
||||
} catch (ExecutionException e) {
|
||||
Util.e("Error while saving tunnel config", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,17 @@
|
||||
package net.i2p.android.i2ptunnel.preferences;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
|
||||
import net.i2p.android.i2ptunnel.util.TunnelLogic;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
@ -110,7 +111,8 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
||||
generalCategory.removePreference(generalCategory.findPreference(getString(R.string.TUNNEL_SHARED_CLIENT)));
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_server_port, portCategory);
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_PORT)));
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
// # TODO: See trac issue #2296
|
||||
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,10 +136,18 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Set<String> interfaceSet = Addresses.getAllAddresses();
|
||||
String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
|
||||
reachableBy.setEntries(interfaces);
|
||||
reachableBy.setEntryValues(interfaces);
|
||||
reachableBy.setEnabled(true);
|
||||
final String[] interfaces = interfaceSet.toArray(new String[interfaceSet.size()]);
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
reachableBy.setEntries(interfaces);
|
||||
reachableBy.setEntryValues(interfaces);
|
||||
reachableBy.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
@ -159,8 +169,11 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
||||
|
||||
@Override
|
||||
protected void generalClientStandardOrIrc(boolean isStandardOrIrc) {
|
||||
/*
|
||||
# TODO: See trac issue #2296
|
||||
if (!isStandardOrIrc)
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -176,7 +189,8 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
||||
@Override
|
||||
protected void generalServerHttpBidirOrStreamr(boolean isStreamr) {
|
||||
addPreferencesFromResource(R.xml.tunnel_gen_client_port, portCategory);
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
// # TODO: See trac issue #2296
|
||||
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
if (isStreamr)
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_LISTEN_PORT)));
|
||||
|
||||
@ -192,7 +206,8 @@ public class GeneralTunnelPreferenceFragment extends BaseTunnelPreferenceFragmen
|
||||
protected void generalServerPortStreamr(boolean isStreamr) {
|
||||
if (isStreamr) {
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_TARGET_HOST)));
|
||||
portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
// # TODO: See trac issue #2296
|
||||
//portCategory.removePreference(portCategory.findPreference(getString(R.string.TUNNEL_USE_SSL)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>> {
|
||||
TunnelControllerGroup mGroup;
|
||||
int mTunnelId;
|
||||
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);
|
||||
}
|
||||
}
|
@ -182,8 +182,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 +214,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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,8 +417,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 +449,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
package net.i2p.android.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
|
||||
public class AdvancedPreferenceFragment extends PreferenceFragment {
|
||||
public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
|
||||
private static final String PREFERENCE_CATEGORY_TRANSPORTS = "preference_category_transports";
|
||||
private static final String PREFERENCE_CATEGORY_EXPL_TUNNELS = "preference_category_expl_tunnels";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
|
||||
findPreference(PREFERENCE_CATEGORY_TRANSPORTS)
|
||||
|
@ -7,8 +7,7 @@ import net.i2p.android.router.SettingsActivity;
|
||||
|
||||
public class AppearancePreferenceFragment extends I2PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_appearance);
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,7 @@ import net.i2p.android.router.SettingsActivity;
|
||||
|
||||
public class ExploratoryPoolPreferenceFragment extends I2PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_expl_tunnels);
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,10 @@ package net.i2p.android.preferences;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
@ -24,8 +24,7 @@ public class GraphsPreferenceFragment extends I2PreferenceFragment {
|
||||
public static final String GRAPH_PREFERENCES_SEEN = "graphPreferencesSeen";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
setupGraphSettings();
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.i2p.android.preferences;
|
||||
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.preferences.util.CustomPreferenceFragment;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -15,7 +15,7 @@ import java.util.Set;
|
||||
/**
|
||||
* A PreferenceFragment that handles saving router settings.
|
||||
*/
|
||||
public class I2PreferenceFragment extends PreferenceFragment {
|
||||
public abstract class I2PreferenceFragment extends CustomPreferenceFragment {
|
||||
@Override
|
||||
public void onPause() {
|
||||
List<Properties> lProps = Util.getPropertiesFromPreferences(getActivity());
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.i2p.android.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
@ -11,8 +11,7 @@ import net.i2p.util.LogManager;
|
||||
|
||||
public class LoggingPreferenceFragment extends I2PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
setupLoggingSettings();
|
||||
}
|
||||
|
@ -7,8 +7,7 @@ import net.i2p.android.router.SettingsActivity;
|
||||
|
||||
public class NetworkPreferenceFragment extends I2PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
}
|
||||
|
||||
|
@ -3,22 +3,21 @@ package net.i2p.android.preferences;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
import net.i2p.android.router.util.PortPreference;
|
||||
import net.i2p.android.preferences.util.PortPreference;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class TransportsPreferenceFragment extends I2PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
// Load any properties that the router might have changed on us.
|
||||
loadProperties();
|
||||
addPreferencesFromResource(R.xml.settings_transports);
|
||||
|
@ -1,9 +1,8 @@
|
||||
package net.i2p.android.router.util;
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.support.v7.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
@ -26,7 +25,6 @@ public class ConnectionLimitPreference extends EditTextPreference {
|
||||
}
|
||||
|
||||
void init(Context context, AttributeSet attrs) {
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ConnectionLimitPreference, 0, 0);
|
||||
mValueInTitle = attr.getBoolean(R.styleable.ConnectionLimitPreference_clp_valueInTitle, false);
|
||||
attr.recycle();
|
||||
@ -70,6 +68,10 @@ public class ConnectionLimitPreference extends EditTextPreference {
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
return value != null && persistInt(Integer.valueOf(value));
|
||||
try {
|
||||
return value != null && persistInt(Integer.valueOf(value));
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class ConnectionLimitPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
|
||||
public static ConnectionLimitPreferenceDialog newInstance(String key) {
|
||||
final ConnectionLimitPreferenceDialog fragment = new ConnectionLimitPreferenceDialog();
|
||||
final Bundle b = new Bundle(1);
|
||||
b.putString(ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
((EditText)view.findViewById(android.R.id.edit)).setInputType(
|
||||
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
|
||||
/**
|
||||
* Handles custom Preferences.
|
||||
*/
|
||||
public abstract class CustomPreferenceFragment extends PreferenceFragmentCompat {
|
||||
private static final String DIALOG_FRAGMENT_TAG =
|
||||
"android.support.v7.preference.PreferenceFragment.DIALOG";
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
// check if dialog is already showing
|
||||
if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DialogFragment f = null;
|
||||
if (preference instanceof ConnectionLimitPreference) {
|
||||
f = ConnectionLimitPreferenceDialog.newInstance(preference.getKey());
|
||||
} else if (preference instanceof IntEditTextPreference) {
|
||||
f = IntEditTextPreferenceDialog.newInstance(preference.getKey());
|
||||
} else if (preference instanceof PortPreference) {
|
||||
f = PortPreferenceDialog.newInstance(preference.getKey());
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
if (f != null) {
|
||||
f.setTargetFragment(this, 0);
|
||||
f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,21 @@
|
||||
package net.i2p.android.router.util;
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.support.v7.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class IntEditTextPreference extends EditTextPreference {
|
||||
|
||||
public IntEditTextPreference(Context context) {
|
||||
super(context);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
public IntEditTextPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -42,6 +38,10 @@ public class IntEditTextPreference extends EditTextPreference {
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
return value != null && persistInt(Integer.valueOf(value));
|
||||
try {
|
||||
return value != null && persistInt(Integer.valueOf(value));
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class IntEditTextPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
|
||||
public static IntEditTextPreferenceDialog newInstance(String key) {
|
||||
final IntEditTextPreferenceDialog fragment = new IntEditTextPreferenceDialog();
|
||||
final Bundle b = new Bundle(1);
|
||||
b.putString(ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
((EditText)view.findViewById(android.R.id.edit)).setInputType(
|
||||
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.i2p.android.router.util;
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class IntListPreference extends ListPreference {
|
@ -1,8 +1,7 @@
|
||||
package net.i2p.android.router.util;
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.support.v7.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
@ -10,17 +9,14 @@ import net.i2p.android.router.R;
|
||||
public class PortPreference extends EditTextPreference {
|
||||
public PortPreference(Context context) {
|
||||
super(context);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
}
|
||||
|
||||
public PortPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
}
|
||||
|
||||
public PortPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
@ -0,0 +1,23 @@
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class PortPreferenceDialog extends EditTextPreferenceDialogFragmentCompat {
|
||||
public static PortPreferenceDialog newInstance(String key) {
|
||||
final PortPreferenceDialog fragment = new PortPreferenceDialog();
|
||||
final Bundle b = new Bundle(1);
|
||||
b.putString(ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
((EditText)view.findViewById(android.R.id.edit)).setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.i2p.android.router.util;
|
||||
package net.i2p.android.preferences.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.support.v7.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class SummaryEditTextPreference extends EditTextPreference {
|
@ -63,13 +63,13 @@ public class ConsoleContainer extends Fragment {
|
||||
startActivity(graphs);
|
||||
}
|
||||
});
|
||||
mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent peers = new Intent(getActivity(), PeersActivity.class);
|
||||
startActivity(peers);
|
||||
}
|
||||
});
|
||||
// mConsoleMenu.findViewById(R.id.action_peers).setOnClickListener(new View.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View view) {
|
||||
// Intent peers = new Intent(getActivity(), PeersActivity.class);
|
||||
// startActivity(peers);
|
||||
// }
|
||||
// });
|
||||
mConsoleMenu.findViewById(R.id.action_netdb).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
@ -87,20 +87,19 @@ public class ConsoleContainer extends Fragment {
|
||||
inflater.inflate(R.menu.activity_main_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
setMenuVisibility();
|
||||
}
|
||||
|
||||
private void setMenuVisibility() {
|
||||
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(
|
||||
"i2pandroid.main.showStats", false);
|
||||
boolean routerRunning = Util.getRouterContext() != null;
|
||||
|
||||
mConsoleMenu.findViewById(R.id.action_logs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
||||
mConsoleMenu.findViewById(R.id.action_graphs).setVisibility(routerRunning ? View.VISIBLE : View.GONE);
|
||||
mConsoleMenu.findViewById(R.id.action_peers).setVisibility(advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (getActivity() != null) {
|
||||
boolean advanced = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
.getBoolean("i2pandroid.main.showStats", false);
|
||||
// mConsoleMenu.findViewById(R.id.action_peers).setVisibility(
|
||||
// advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||
mConsoleMenu.findViewById(R.id.action_netdb).setVisibility(
|
||||
advanced && routerRunning ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -428,9 +428,9 @@ public class MainFragment extends I2PFragmentBase {
|
||||
double outData = ctx.bandwidthLimiter().getTotalAllocatedOutboundBytes();
|
||||
|
||||
((TextView) getActivity().findViewById(R.id.console_download_stats)).setText(
|
||||
Util.formatSize(inBw) + "Bps / " + Util.formatSize(inData) + "B");
|
||||
Util.formatSpeed(inBw) + "Bps / " + Util.formatSize(inData) + "B");
|
||||
((TextView) getActivity().findViewById(R.id.console_upload_stats)).setText(
|
||||
Util.formatSize(outBw) + "Bps / " + Util.formatSize(outData) + "B");
|
||||
Util.formatSpeed(outBw) + "Bps / " + Util.formatSize(outData) + "B");
|
||||
|
||||
getActivity().findViewById(R.id.console_usage_stats).setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
@ -531,7 +531,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
|
||||
public AlphaComparator(RouterContext ctx) {
|
||||
_ctx = ctx;
|
||||
xsc = _(ctx, SHARED_CLIENTS);
|
||||
xsc = _t(ctx, SHARED_CLIENTS);
|
||||
}
|
||||
|
||||
public int compare(Destination lhs, Destination rhs) {
|
||||
@ -559,12 +559,12 @@ public class MainFragment extends I2PFragmentBase {
|
||||
if (name == null)
|
||||
name = d.calculateHash().toBase64().substring(0, 6);
|
||||
else
|
||||
name = _(ctx, name);
|
||||
name = _t(ctx, name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private String _(RouterContext ctx, String s) {
|
||||
private String _t(RouterContext ctx, String s) {
|
||||
if (SHARED_CLIENTS.equals(s))
|
||||
return getString(R.string.shared_clients);
|
||||
else
|
||||
@ -648,9 +648,11 @@ public class MainFragment extends I2PFragmentBase {
|
||||
private void checkFirstStart() {
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
boolean firstStart = ab.getPref(PREF_FIRST_START, true);
|
||||
if (firstStart) {
|
||||
// Check ab.isFinishing() because DialogFragment.show() will throw IllegalStateException if
|
||||
// called after ab.onSaveInstanceState().
|
||||
if (firstStart && !ab.isFinishing()) {
|
||||
FirstStartDialog dialog = new FirstStartDialog();
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "firststart");
|
||||
dialog.show(ab.getSupportFragmentManager(), "firststart");
|
||||
ab.setPref(PREF_FIRST_START, false);
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ package net.i2p.android.router;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
import net.i2p.android.I2PActivity;
|
||||
@ -84,10 +84,11 @@ public class SettingsActivity extends AppCompatActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragment {
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
public void onCreatePreferences(Bundle paramBundle, String s) {
|
||||
migrateOldSettings();
|
||||
|
||||
addPreferencesFromResource(R.xml.settings);
|
||||
|
||||
this.findPreference(PREFERENCE_CATEGORY_NETWORK)
|
||||
@ -104,6 +105,29 @@ public class SettingsActivity extends AppCompatActivity implements
|
||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
||||
}
|
||||
|
||||
private void migrateOldSettings() {
|
||||
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||
try {
|
||||
prefs.getInt("i2np.bandwidth.inboundKBytesPerSecond", 0);
|
||||
} catch (ClassCastException e) {
|
||||
// Migrate pre-0.9.25 settings
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.remove("i2np.bandwidth.inboundKBytesPerSecond");
|
||||
editor.putInt("i2np.bandwidth.inboundKBytesPerSecond", Integer.parseInt(
|
||||
prefs.getString("i2np.bandwidth.inboundKBytesPerSecond", "100")));
|
||||
editor.remove("i2np.bandwidth.outboundKBytesPerSecond");
|
||||
editor.putInt("i2np.bandwidth.outboundKBytesPerSecond", Integer.parseInt(
|
||||
prefs.getString("i2np.bandwidth.outboundKBytesPerSecond", "100")));
|
||||
editor.remove("i2np.ntcp.maxConnections");
|
||||
editor.putInt("i2np.ntcp.maxConnections", Integer.parseInt(
|
||||
prefs.getString("i2np.ntcp.maxConnections", "32")));
|
||||
editor.remove("i2np.udp.maxConnections");
|
||||
editor.putInt("i2np.udp.maxConnections", Integer.parseInt(
|
||||
prefs.getString("i2np.udp.maxConnections", "32")));
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,11 +144,14 @@ public class AddressbookContainer extends Fragment
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.container_addressbook_actions, menu);
|
||||
SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
|
||||
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));
|
||||
searchView.setOnQueryTextListener(this);
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search_addressbook);
|
||||
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
|
||||
searchView.setOnQueryTextListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -2,6 +2,10 @@ package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
@ -14,6 +18,8 @@ import android.widget.TextView;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FirstStartDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
@ -23,8 +29,20 @@ public class FirstStartDialog extends DialogFragment {
|
||||
|
||||
TextView tv = (TextView)view.findViewById(R.id.url_faq);
|
||||
Linkify.addLinks(tv, I2Patterns.I2P_WEB_URL, "http://");
|
||||
tv = (TextView)view.findViewById(R.id.url_irc_i2p);
|
||||
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
|
||||
|
||||
// Find all installed browsers that listen for "irc://"
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("irc://127.0.0.1:6668/i2p"));
|
||||
final PackageManager pm = getActivity().getPackageManager();
|
||||
List<ResolveInfo> installedIrcClients = pm.queryIntentActivities(intent, 0);
|
||||
|
||||
// Only linkify "irc://" if we have an app that can handle them.
|
||||
// Otherwise, the app crashes with an un-catchable ActivityNotFoundException
|
||||
// if the user clicks one of them.
|
||||
if (installedIrcClients.size() > 0) {
|
||||
tv = (TextView) view.findViewById(R.id.url_irc_i2p);
|
||||
Linkify.addLinks(tv, I2Patterns.IRC_URL, "irc://");
|
||||
}
|
||||
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.first_start_title)
|
||||
|
@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import net.i2p.android.router.I2PConstants;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
@ -19,7 +20,12 @@ public class OnBootReceiver extends BroadcastReceiver implements I2PConstants {
|
||||
|
||||
if (startOnBoot) {
|
||||
Intent routerService = new Intent(context, RouterService.class);
|
||||
context.startService(routerService);
|
||||
// Ticket #2404
|
||||
try {
|
||||
context.startService(routerService);
|
||||
} catch (IllegalStateException ex) {
|
||||
Util.e("Error: ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,8 @@ import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterLaunch;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Runs the router
|
||||
@ -214,16 +212,18 @@ public class RouterService extends Service {
|
||||
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
|
||||
|
||||
// Launch the router!
|
||||
RouterLaunch.main(null);
|
||||
// TODO Store this somewhere instead of relying on global context?
|
||||
Router r = new Router();
|
||||
r.runRouter();
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.STARTING) {
|
||||
return;
|
||||
}
|
||||
setState(State.RUNNING);
|
||||
_statusBar.replace(StatusBar.ICON_RUNNING, R.string.notification_status_running);
|
||||
_context = Util.getRouterContext();
|
||||
_context = r.getContext();
|
||||
if (_context == null) {
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
throw new IllegalStateException("Router has no context?");
|
||||
}
|
||||
_context.router().setKillVMOnEnd(false);
|
||||
Job loadJob = new LoadClientsJob(RouterService.this, _context, _notif);
|
||||
@ -260,26 +260,16 @@ public class RouterService extends Service {
|
||||
int inCl = ctx.tunnelManager().getInboundClientTunnelCount();
|
||||
int outCl = ctx.tunnelManager().getOutboundClientTunnelCount();
|
||||
String uptime = DataHelper.formatDuration(ctx.router().getUptime());
|
||||
double inBW = ctx.bandwidthLimiter().getReceiveBps() / 1024;
|
||||
double outBW = ctx.bandwidthLimiter().getSendBps() / 1024;
|
||||
// control total width
|
||||
DecimalFormat fmt;
|
||||
if(inBW >= 1000 || outBW >= 1000) {
|
||||
fmt = new DecimalFormat("#0");
|
||||
} else if(inBW >= 100 || outBW >= 100) {
|
||||
fmt = new DecimalFormat("#0.0");
|
||||
} else {
|
||||
fmt = new DecimalFormat("#0.00");
|
||||
}
|
||||
double inBW = ctx.bandwidthLimiter().getReceiveBps();
|
||||
double outBW = ctx.bandwidthLimiter().getSendBps();
|
||||
|
||||
String text =
|
||||
getResources().getString(R.string.notification_status_text,
|
||||
Util.formatSize(inBW), Util.formatSize(outBW));
|
||||
Util.formatSpeed(inBW), Util.formatSpeed(outBW));
|
||||
|
||||
// TODO change string resource after 0.9.20 to use Util.formatSize()
|
||||
String bigText =
|
||||
getResources().getString(R.string.notification_status_bw,
|
||||
fmt.format(inBW), fmt.format(outBW)) + '\n'
|
||||
Util.formatSpeed(inBW), Util.formatSpeed(outBW)) + '\n'
|
||||
+ getResources().getString(R.string.notification_status_peers,
|
||||
active, known) + '\n'
|
||||
+ getResources().getString(R.string.notification_status_expl,
|
||||
|
@ -40,6 +40,7 @@ class StatusBar {
|
||||
mNotifyBuilder = new NotificationCompat.Builder(ctx)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(icon)
|
||||
.setColor(mCtx.getResources().getColor(R.color.primary_light))
|
||||
.setOngoing(true)
|
||||
.setOnlyAlertOnce(true);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
@ -18,7 +19,7 @@ public class Notifications {
|
||||
mNotificationManager = (NotificationManager) ctx.getSystemService(
|
||||
Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
|
||||
public void notify(String title, String text) {
|
||||
notify(title, text, null);
|
||||
}
|
||||
@ -26,10 +27,11 @@ public class Notifications {
|
||||
public void notify(String title, String text, Class<?> c) {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(mCtx)
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(ICON)
|
||||
.setAutoCancel(true);
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(ICON)
|
||||
.setColor(mCtx.getResources().getColor(R.color.primary_light))
|
||||
.setAutoCancel(true);
|
||||
|
||||
if (c != null) {
|
||||
Intent intent = new Intent(mCtx, c);
|
||||
|
@ -299,7 +299,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -553,8 +561,19 @@ public abstract class Util implements I2PConstants {
|
||||
}
|
||||
|
||||
public static String formatSize(double size) {
|
||||
return formatSize(size, 0);
|
||||
}
|
||||
|
||||
public static String formatSpeed(double size) {
|
||||
return formatSize(size, 1);
|
||||
}
|
||||
|
||||
public static String formatSize(double size, int baseScale) {
|
||||
int scale;
|
||||
for (scale = 0; size >= 1024.0D; size /= 1024.0D) {
|
||||
for (int i = 0; i < baseScale; i++) {
|
||||
size /= 1024.0D;
|
||||
}
|
||||
for (scale = baseScale; size >= 1024.0D; size /= 1024.0D) {
|
||||
++scale;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package net.i2p.android.router.web;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.webkit.HttpAuthHandler;
|
||||
@ -29,6 +31,7 @@ import java.io.OutputStream;
|
||||
|
||||
public class I2PWebViewClient extends WebViewClient {
|
||||
|
||||
private Fragment _parentFrag;
|
||||
private BGLoad _lastTask;
|
||||
/** save it here so we can dismiss it in onPageFinished() */
|
||||
private ProgressDialog _lastDialog;
|
||||
@ -40,6 +43,11 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
private static final String ERROR_URL = "<p>Unable to load URL: ";
|
||||
private static final String ERROR_ROUTER = "<p>Your router (or the HTTP proxy) does not appear to be running.</p>";
|
||||
|
||||
public I2PWebViewClient(Fragment parentFrag) {
|
||||
super();
|
||||
_parentFrag = parentFrag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
Util.d("Should override? " + url);
|
||||
@ -102,7 +110,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
///////// API 8
|
||||
// Otherwise hangs waiting for CSS
|
||||
view.getSettings().setBlockNetworkLoads(false);
|
||||
_lastDialog = new ProgressDialog(view.getContext());
|
||||
_lastDialog = new ProgressDialog(_parentFrag.getContext());
|
||||
BGLoad task = new BackgroundEepLoad(view, h, _lastDialog);
|
||||
_lastTask = task;
|
||||
task.execute(url);
|
||||
@ -215,10 +223,12 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
|
||||
private abstract static class BGLoad extends AsyncTask<String, Integer, Integer> implements DialogInterface.OnCancelListener {
|
||||
protected final WebView _view;
|
||||
protected final Context _ctx;
|
||||
protected final ProgressDialog _dialog;
|
||||
|
||||
public BGLoad(WebView view, ProgressDialog dialog) {
|
||||
_view = view;
|
||||
_ctx = view.getContext();
|
||||
if (dialog != null)
|
||||
dialog.setCancelable(true);
|
||||
_dialog = dialog;
|
||||
@ -305,9 +315,9 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
protected Integer doInBackground(String... urls) {
|
||||
final String url = urls[0];
|
||||
Uri uri = Uri.parse(url);
|
||||
File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri);
|
||||
File cacheFile = AppCache.getInstance(_ctx).getCacheFile(uri);
|
||||
if (cacheFile.exists()) {
|
||||
final Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri);
|
||||
final Uri resUri = AppCache.getInstance(_ctx).getCacheUri(uri);
|
||||
Util.d("Loading " + url + " from resource cache " + resUri);
|
||||
_view.post(new Runnable() {
|
||||
@Override
|
||||
@ -325,7 +335,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
//EepGetFetcher fetcher = new EepGetFetcher(url);
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = AppCache.getInstance(_view.getContext()).createCacheFile(uri);
|
||||
out = AppCache.getInstance(_ctx).createCacheFile(uri);
|
||||
// write error to stream
|
||||
EepGetFetcher fetcher = new EepGetFetcher(url, out, true);
|
||||
fetcher.addStatusListener(this);
|
||||
@ -338,11 +348,11 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
if (success) {
|
||||
// store in cache, get content URL, and load that way
|
||||
// Set as current base
|
||||
final Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri, true);
|
||||
final Uri content = AppCache.getInstance(_ctx).addCacheFile(uri, true);
|
||||
if (content != null) {
|
||||
Util.d("Stored cache in " + content);
|
||||
} else {
|
||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
||||
AppCache.getInstance(_ctx).removeCacheFile(uri);
|
||||
Util.d("cache create error");
|
||||
return 0;
|
||||
}
|
||||
@ -381,7 +391,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
AppCache.getInstance(_view.getContext()).removeCacheFile(uri);
|
||||
AppCache.getInstance(_ctx).removeCacheFile(uri);
|
||||
Util.d("loading error data URL: " + url);
|
||||
final String finalMsg = msg;
|
||||
_view.post(new Runnable() {
|
||||
@ -403,30 +413,35 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
if (isCancelled())
|
||||
return;
|
||||
int prog = progress[0];
|
||||
if (prog < 0) {
|
||||
_dialog.setTitle("Contacting...");
|
||||
_dialog.setMessage(_host);
|
||||
_dialog.setIndeterminate(true);
|
||||
_dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
_dialog.setOnCancelListener(this);
|
||||
_dialog.show();
|
||||
} else if (prog == 0 && _total > 0) {
|
||||
_dialog.setTitle("Downloading...");
|
||||
_dialog.setMessage("...from " + _host);
|
||||
_dialog.setIndeterminate(false);
|
||||
_dialog.setMax(_total);
|
||||
_dialog.setProgress(0);
|
||||
} else if (_total > 0) {
|
||||
// so it isn't at 100% while loading images and CSS
|
||||
_dialog.setProgress(Math.min(prog, _total * 99 / 100));
|
||||
} else if (prog > 0) {
|
||||
// ugly, need custom
|
||||
_dialog.setTitle("Downloading...");
|
||||
_dialog.setMessage("...from " + _host + ": " + DataHelper.formatSize(prog) + 'B');
|
||||
//_dialog.setProgress(prog);
|
||||
} else {
|
||||
// nothing
|
||||
try {
|
||||
int prog = progress[0];
|
||||
if (prog < 0) {
|
||||
_dialog.setTitle("Contacting...");
|
||||
_dialog.setMessage(_host);
|
||||
_dialog.setIndeterminate(true);
|
||||
_dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
_dialog.setOnCancelListener(this);
|
||||
_dialog.show();
|
||||
} else if (prog == 0 && _total > 0) {
|
||||
_dialog.setTitle("Downloading...");
|
||||
_dialog.setMessage("...from " + _host);
|
||||
_dialog.setIndeterminate(false);
|
||||
_dialog.setMax(_total);
|
||||
_dialog.setProgress(0);
|
||||
} else if (_total > 0) {
|
||||
// so it isn't at 100% while loading images and CSS
|
||||
_dialog.setProgress(Math.min(prog, _total * 99 / 100));
|
||||
} else if (prog > 0) {
|
||||
// ugly, need custom
|
||||
_dialog.setTitle("Downloading...");
|
||||
_dialog.setMessage("...from " + _host + ": " + DataHelper.formatSize(prog) + 'B');
|
||||
//_dialog.setProgress(prog);
|
||||
} else {
|
||||
// nothing
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// throws IAE - not attached to window manager - perhaps due to screen rotation?
|
||||
Util.e("Error while updating I2PWebViewClient dialog", iae);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ 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
|
||||
|
@ -1,49 +0,0 @@
|
||||
package net.i2p.android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class CustomViewPager extends ViewPager {
|
||||
private boolean mEnabled;
|
||||
|
||||
public CustomViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return mEnabled && super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
return mEnabled && super.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentItem(int item) {
|
||||
if (mEnabled || item == 0)
|
||||
super.setCurrentItem(item);
|
||||
else
|
||||
Toast.makeText(getContext(), Util.getRouterContext() == null ?
|
||||
R.string.router_not_running : R.string.router_shutting_down,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void setPagingEnabled(boolean enabled) {
|
||||
mEnabled = enabled;
|
||||
updatePagingState();
|
||||
}
|
||||
|
||||
public void updatePagingState() {
|
||||
if (!mEnabled && getCurrentItem() != 0)
|
||||
setCurrentItem(0);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -190,6 +190,10 @@ public class I2PB64DestinationFragment extends Fragment {
|
||||
Util.e("Could not find B64 file", fnfe);
|
||||
Toast.makeText(getActivity(), "Could not find B64 file.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} catch (SecurityException se) {
|
||||
Util.e("Could not open B64 file", se);
|
||||
Toast.makeText(getActivity(), "Could not open B64 file.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} finally {
|
||||
if (br != null)
|
||||
try {
|
||||
|
@ -0,0 +1,50 @@
|
||||
package net.i2p.router.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.client.DomainSocketFactory;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Unix domain socket version of ClientListenerRunner.
|
||||
*
|
||||
* @author str4d
|
||||
* @since 0.9.14
|
||||
*/
|
||||
public class DomainClientListenerRunner extends ClientListenerRunner {
|
||||
private final DomainSocketFactory factory;
|
||||
private final Log _log;
|
||||
|
||||
public DomainClientListenerRunner(RouterContext context, ClientManager manager) {
|
||||
super(context, manager, -1);
|
||||
factory = new DomainSocketFactory(_context);
|
||||
_log = context.logManager().getLog(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected ServerSocket getServerSocket() throws IOException {
|
||||
return factory.createServerSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopListening() {
|
||||
_running = false;
|
||||
// LocalServerSocket.close() fails silently if the socket is blocking in accept(), so we
|
||||
// trick the socket by opening a new connection and then immediately closing it.
|
||||
// http://stackoverflow.com/questions/8007982/java-serversocket-and-android-localserversocket
|
||||
try {
|
||||
_log.debug("Connecting to domain socket to trigger close");
|
||||
Socket s = factory.createSocket(DomainSocketFactory.I2CP_SOCKET_ADDRESS);
|
||||
s.close();
|
||||
} catch (IOException e) {
|
||||
_log.error("Failed to connect to domain socket to trigger close", e);
|
||||
}
|
||||
// runServer() will close the LocalServerSocket.
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013 Mohammed Lakkadshaw
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.htmltextview;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.style.AlignmentSpan;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.text.style.LeadingMarginSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Some parts of this code are based on android.text.Html
|
||||
*/
|
||||
public class HtmlTagHandler implements Html.TagHandler {
|
||||
private int mListItemCount = 0;
|
||||
private Vector<String> mListParents = new Vector<String>();
|
||||
|
||||
private static class Code {
|
||||
}
|
||||
|
||||
private static class Center {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
|
||||
if (opening) {
|
||||
// opening tag
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "opening, output: " + output.toString());
|
||||
}
|
||||
|
||||
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) {
|
||||
mListParents.add(tag);
|
||||
mListItemCount = 0;
|
||||
} else if (tag.equalsIgnoreCase("code")) {
|
||||
start(output, new Code());
|
||||
} else if (tag.equalsIgnoreCase("center")) {
|
||||
start(output, new Center());
|
||||
}
|
||||
} else {
|
||||
// closing tag
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "closing, output: " + output.toString());
|
||||
}
|
||||
|
||||
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) {
|
||||
mListParents.remove(tag);
|
||||
mListItemCount = 0;
|
||||
} else if (tag.equalsIgnoreCase("li")) {
|
||||
handleListTag(output);
|
||||
} else if (tag.equalsIgnoreCase("code")) {
|
||||
end(output, Code.class, new TypefaceSpan("monospace"), false);
|
||||
} else if (tag.equalsIgnoreCase("center")) {
|
||||
end(output, Center.class, new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the opening tag by using private classes
|
||||
*
|
||||
* @param output
|
||||
* @param mark
|
||||
*/
|
||||
private void start(Editable output, Object mark) {
|
||||
int len = output.length();
|
||||
output.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
|
||||
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "len: " + len);
|
||||
}
|
||||
}
|
||||
|
||||
private void end(Editable output, Class kind, Object repl, boolean paragraphStyle) {
|
||||
Object obj = getLast(output, kind);
|
||||
// start of the tag
|
||||
int where = output.getSpanStart(obj);
|
||||
// end of the tag
|
||||
int len = output.length();
|
||||
|
||||
output.removeSpan(obj);
|
||||
|
||||
if (where != len) {
|
||||
// paragraph styles like AlignmentSpan need to end with a new line!
|
||||
if (paragraphStyle) {
|
||||
output.append("\n");
|
||||
len++;
|
||||
}
|
||||
output.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "where: " + where);
|
||||
Log.d(HtmlTextView.TAG, "len: " + len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last marked position of a specific tag kind (private class)
|
||||
*
|
||||
* @param text
|
||||
* @param kind
|
||||
* @return
|
||||
*/
|
||||
private Object getLast(Editable text, Class kind) {
|
||||
Object[] objs = text.getSpans(0, text.length(), kind);
|
||||
if (objs.length == 0) {
|
||||
return null;
|
||||
} else {
|
||||
for (int i = objs.length; i > 0; i--) {
|
||||
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
|
||||
return objs[i - 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleListTag(Editable output) {
|
||||
if (mListParents.lastElement().equals("ul")) {
|
||||
output.append("\n");
|
||||
String[] split = output.toString().split("\n");
|
||||
|
||||
int lastIndex = split.length - 1;
|
||||
int start = output.length() - split[lastIndex].length() - 1;
|
||||
output.setSpan(new BulletSpan(15 * mListParents.size()), start, output.length(), 0);
|
||||
} else if (mListParents.lastElement().equals("ol")) {
|
||||
mListItemCount++;
|
||||
|
||||
output.append("\n");
|
||||
String[] split = output.toString().split("\n");
|
||||
|
||||
int lastIndex = split.length - 1;
|
||||
int start = output.length() - split[lastIndex].length() - 1;
|
||||
output.insert(start, mListItemCount + ". ");
|
||||
output.setSpan(new LeadingMarginSpan.Standard(15 * mListParents.size()), start, output.length(), 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.htmltextview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class HtmlTextView extends JellyBeanSpanFixTextView {
|
||||
|
||||
public static final String TAG = "HtmlTextView";
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
public HtmlTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public HtmlTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public HtmlTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
|
||||
*
|
||||
* @param is
|
||||
* @return
|
||||
*/
|
||||
static private String convertStreamToString(java.io.InputStream is) {
|
||||
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads HTML from a raw resource, i.e., a HTML file in res/raw/.
|
||||
* This allows translatable resource (e.g., res/raw-de/ for german).
|
||||
* The containing HTML is parsed to Android's Spannable format and then displayed.
|
||||
*
|
||||
* @param context
|
||||
* @param id for example: R.raw.help
|
||||
*/
|
||||
public void setHtmlFromRawResource(Context context, int id, boolean useLocalDrawables) {
|
||||
// load html from html file from /res/raw
|
||||
InputStream inputStreamText = context.getResources().openRawResource(id);
|
||||
|
||||
setHtmlFromString(convertStreamToString(inputStreamText), useLocalDrawables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses String containing HTML to Android's Spannable format and displays it in this TextView.
|
||||
*
|
||||
* @param html String containing HTML, for example: "<b>Hello world!</b>"
|
||||
*/
|
||||
public void setHtmlFromString(String html, boolean useLocalDrawables) {
|
||||
Html.ImageGetter imgGetter;
|
||||
if (useLocalDrawables) {
|
||||
imgGetter = new LocalImageGetter(getContext());
|
||||
} else {
|
||||
imgGetter = new UrlImageGetter(this, getContext());
|
||||
}
|
||||
// this uses Android's Html class for basic parsing, and HtmlTagHandler
|
||||
setText(Html.fromHtml(html, imgGetter, new HtmlTagHandler()));
|
||||
|
||||
// make links work
|
||||
setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// no flickering when clicking textview for Android < 4, but overriders color...
|
||||
// text.setTextColor(getResources().getColor(android.R.color.secondary_text_dark_nodisable));
|
||||
}
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012 Pierre-Yves Ricau <py.ricau@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.htmltextview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p/>
|
||||
* A {@link android.widget.TextView} that insert spaces around its text spans where needed to prevent
|
||||
* {@link IndexOutOfBoundsException} in {@link #onMeasure(int, int)} on Jelly Bean.
|
||||
* <p/>
|
||||
* When {@link #onMeasure(int, int)} throws an exception, we try to fix the text by adding spaces
|
||||
* around spans, until it works again. We then try removing some of the added spans, to minimize the
|
||||
* insertions.
|
||||
* <p/>
|
||||
* The fix is time consuming (a few ms, it depends on the size of your text), but it should only
|
||||
* happen once per text change.
|
||||
* <p/>
|
||||
* See http://code.google.com/p/android/issues/detail?id=35466
|
||||
*/
|
||||
public class JellyBeanSpanFixTextView extends TextView {
|
||||
|
||||
private static class FixingResult {
|
||||
public final boolean fixed;
|
||||
public final List<Object> spansWithSpacesBefore;
|
||||
public final List<Object> spansWithSpacesAfter;
|
||||
|
||||
public static FixingResult fixed(List<Object> spansWithSpacesBefore,
|
||||
List<Object> spansWithSpacesAfter) {
|
||||
return new FixingResult(true, spansWithSpacesBefore, spansWithSpacesAfter);
|
||||
}
|
||||
|
||||
public static FixingResult notFixed() {
|
||||
return new FixingResult(false, null, null);
|
||||
}
|
||||
|
||||
private FixingResult(boolean fixed, List<Object> spansWithSpacesBefore,
|
||||
List<Object> spansWithSpacesAfter) {
|
||||
this.fixed = fixed;
|
||||
this.spansWithSpacesBefore = spansWithSpacesBefore;
|
||||
this.spansWithSpacesAfter = spansWithSpacesAfter;
|
||||
}
|
||||
}
|
||||
|
||||
public JellyBeanSpanFixTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public JellyBeanSpanFixTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public JellyBeanSpanFixTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
try {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
fixOnMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, fixes the Spanned text by adding spaces around spans when needed.
|
||||
*/
|
||||
private void fixOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
CharSequence text = getText();
|
||||
if (text instanceof Spanned) {
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(text);
|
||||
fixSpannedWithSpaces(builder, widthMeasureSpec, heightMeasureSpec);
|
||||
} else {
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "The text isn't a Spanned");
|
||||
}
|
||||
fallbackToString(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add spaces around spans until the text is fixed, and then removes the unneeded spaces
|
||||
*/
|
||||
private void fixSpannedWithSpaces(SpannableStringBuilder builder, int widthMeasureSpec,
|
||||
int heightMeasureSpec) {
|
||||
long startFix = System.currentTimeMillis();
|
||||
|
||||
FixingResult result = addSpacesAroundSpansUntilFixed(builder, widthMeasureSpec,
|
||||
heightMeasureSpec);
|
||||
|
||||
if (result.fixed) {
|
||||
removeUnneededSpaces(widthMeasureSpec, heightMeasureSpec, builder, result);
|
||||
} else {
|
||||
fallbackToString(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
if (HtmlTextView.DEBUG) {
|
||||
long fixDuration = System.currentTimeMillis() - startFix;
|
||||
Log.d(HtmlTextView.TAG, "fixSpannedWithSpaces() duration in ms: " + fixDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private FixingResult addSpacesAroundSpansUntilFixed(SpannableStringBuilder builder,
|
||||
int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
Object[] spans = builder.getSpans(0, builder.length(), Object.class);
|
||||
List<Object> spansWithSpacesBefore = new ArrayList<Object>(spans.length);
|
||||
List<Object> spansWithSpacesAfter = new ArrayList<Object>(spans.length);
|
||||
|
||||
for (Object span : spans) {
|
||||
int spanStart = builder.getSpanStart(span);
|
||||
if (isNotSpace(builder, spanStart - 1)) {
|
||||
builder.insert(spanStart, " ");
|
||||
spansWithSpacesBefore.add(span);
|
||||
}
|
||||
|
||||
int spanEnd = builder.getSpanEnd(span);
|
||||
if (isNotSpace(builder, spanEnd)) {
|
||||
builder.insert(spanEnd, " ");
|
||||
spansWithSpacesAfter.add(span);
|
||||
}
|
||||
|
||||
try {
|
||||
setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
|
||||
return FixingResult.fixed(spansWithSpacesBefore, spansWithSpacesAfter);
|
||||
} catch (IndexOutOfBoundsException notFixed) {
|
||||
}
|
||||
}
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "Could not fix the Spanned by adding spaces around spans");
|
||||
}
|
||||
return FixingResult.notFixed();
|
||||
}
|
||||
|
||||
private boolean isNotSpace(CharSequence text, int where) {
|
||||
return where < 0 || text.charAt(where) != ' ';
|
||||
}
|
||||
|
||||
private void setTextAndMeasure(CharSequence text, int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setText(text);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private void removeUnneededSpaces(int widthMeasureSpec, int heightMeasureSpec,
|
||||
SpannableStringBuilder builder, FixingResult result) {
|
||||
|
||||
for (Object span : result.spansWithSpacesAfter) {
|
||||
int spanEnd = builder.getSpanEnd(span);
|
||||
builder.delete(spanEnd, spanEnd + 1);
|
||||
try {
|
||||
setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
builder.insert(spanEnd, " ");
|
||||
}
|
||||
}
|
||||
|
||||
boolean needReset = true;
|
||||
for (Object span : result.spansWithSpacesBefore) {
|
||||
int spanStart = builder.getSpanStart(span);
|
||||
builder.delete(spanStart - 1, spanStart);
|
||||
try {
|
||||
setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec);
|
||||
needReset = false;
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
needReset = true;
|
||||
int newSpanStart = spanStart - 1;
|
||||
builder.insert(newSpanStart, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (needReset) {
|
||||
setText(builder);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void fallbackToString(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (HtmlTextView.DEBUG) {
|
||||
Log.d(HtmlTextView.TAG, "Fallback to unspanned text");
|
||||
}
|
||||
String fallbackText = getText().toString();
|
||||
setTextAndMeasure(fallbackText, widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 drawk
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.htmltextview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Copied from http://stackoverflow.com/a/22298833
|
||||
*/
|
||||
public class LocalImageGetter implements Html.ImageGetter {
|
||||
Context c;
|
||||
|
||||
public LocalImageGetter(Context c) {
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public Drawable getDrawable(String source) {
|
||||
int id = c.getResources().getIdentifier(source, "drawable", c.getPackageName());
|
||||
|
||||
if (id == 0) {
|
||||
// the drawable resource wasn't found in our package, maybe it is a stock android drawable?
|
||||
id = c.getResources().getIdentifier(source, "drawable", "android");
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
// prevent a crash if the resource still can't be found
|
||||
Log.e(HtmlTextView.TAG, "source could not be found: " + source);
|
||||
return null;
|
||||
} else {
|
||||
Drawable d = c.getResources().getDrawable(id);
|
||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Antarix Tandon
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.htmltextview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.Html.ImageGetter;
|
||||
import android.view.View;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
public class UrlImageGetter implements ImageGetter {
|
||||
Context c;
|
||||
View container;
|
||||
|
||||
/**
|
||||
* Construct the URLImageParser which will execute AsyncTask and refresh the container
|
||||
*
|
||||
* @param t
|
||||
* @param c
|
||||
*/
|
||||
public UrlImageGetter(View t, Context c) {
|
||||
this.c = c;
|
||||
this.container = t;
|
||||
}
|
||||
|
||||
public Drawable getDrawable(String source) {
|
||||
UrlDrawable urlDrawable = new UrlDrawable();
|
||||
|
||||
// get the actual source
|
||||
ImageGetterAsyncTask asyncTask = new ImageGetterAsyncTask(urlDrawable);
|
||||
|
||||
asyncTask.execute(source);
|
||||
|
||||
// return reference to URLDrawable which will asynchronously load the image specified in the src tag
|
||||
return urlDrawable;
|
||||
}
|
||||
|
||||
public class ImageGetterAsyncTask extends AsyncTask<String, Void, Drawable> {
|
||||
UrlDrawable urlDrawable;
|
||||
|
||||
public ImageGetterAsyncTask(UrlDrawable d) {
|
||||
this.urlDrawable = d;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Drawable doInBackground(String... params) {
|
||||
String source = params[0];
|
||||
return fetchDrawable(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Drawable result) {
|
||||
// set the correct bound according to the result from HTTP call
|
||||
urlDrawable.setBounds(0, 0, result.getIntrinsicWidth(), result.getIntrinsicHeight());
|
||||
|
||||
// change the reference of the current drawable to the result from the HTTP call
|
||||
urlDrawable.drawable = result;
|
||||
|
||||
// redraw the image by invalidating the container
|
||||
UrlImageGetter.this.container.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Drawable from URL
|
||||
*
|
||||
* @param urlString
|
||||
* @return
|
||||
*/
|
||||
public Drawable fetchDrawable(String urlString) {
|
||||
try {
|
||||
InputStream is = fetch(urlString);
|
||||
Drawable drawable = Drawable.createFromStream(is, "src");
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
return drawable;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream fetch(String urlString) throws MalformedURLException, IOException {
|
||||
DefaultHttpClient httpClient = new DefaultHttpClient();
|
||||
HttpGet request = new HttpGet(urlString);
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
return response.getEntity().getContent();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class UrlDrawable extends BitmapDrawable {
|
||||
protected Drawable drawable;
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
// override the draw to facilitate refresh function later
|
||||
if (drawable != null) {
|
||||
drawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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 |