Compare commits
167 Commits
android-cl
...
android-0.
Author | SHA1 | Date | |
---|---|---|---|
17ab043a4b | |||
32b2b0ce75 | |||
b77e2ebbe5 | |||
9eeab68cdb | |||
96257015a9 | |||
d7f6e3688c | |||
5ef434e29f | |||
852d695dac | |||
96cb8ab410 | |||
cd158cca84 | |||
b71a0a27d3 | |||
64268c7af8 | |||
95749f032e | |||
0ac1ae56b0 | |||
c52bc45910 | |||
064ebc6857 | |||
6352cd9412 | |||
db6c74b4b8 | |||
a8d699bea2 | |||
5d9cb0029f | |||
51f7fca1ea | |||
c61ccd32ba | |||
1debd64bc3 | |||
561bcfe3fa | |||
1c1f77f5c5 | |||
420526a7c4 | |||
99496be412 | |||
b6f1cdc769 | |||
75e4153f4e | |||
eefa5b8064 | |||
9d32e44547 | |||
42a0d552c7 | |||
65848dd22b | |||
610de188a4 | |||
f1cd3032c5 | |||
1a922ba04a | |||
bdd59734ec | |||
fe162e4f5c | |||
69e30e97b8 | |||
513fbe0c9f | |||
de23a76e6b | |||
c9936894d8 | |||
cb6efd9ed8 | |||
ad245003bf | |||
9f27aedc49 | |||
d8f883dce8 | |||
7db1fbac94 | |||
19464124d6 | |||
5ba616facc | |||
590a8183aa | |||
9a45bbd18c | |||
c5eedc0a5e | |||
715302c813 | |||
3327ed722a | |||
40afd69a54 | |||
241381c7fa | |||
7a7ba093db | |||
f0e6744760 | |||
99002c1c5c | |||
605a6c1cf4 | |||
954a9cc46b | |||
1ef838b966 | |||
c672ca05f5 | |||
c493e73889 | |||
2b7c280f5b | |||
23eab8a90a | |||
c59103eb76 | |||
f00a35ee09 | |||
af93725c01 | |||
3953301c57 | |||
2dab9d5d4f | |||
b77666fa26 | |||
eca931c1b5 | |||
86ae77701f | |||
c1ee0c4d9e | |||
e632b35862 | |||
20d2dcd891 | |||
61d5ba5a7c | |||
339f688b7c | |||
fed11e703a | |||
438df8142a | |||
7b3730be24 | |||
d6c20bafb3 | |||
b8998db3a3 | |||
9ab1c84878 | |||
2f3335d361 | |||
0e8d900ed4 | |||
2c7ce0b7c6 | |||
3e2bdacf89 | |||
64c32076a1 | |||
4d75ce7de1 | |||
269ae2f569 | |||
a42a4b2c99 | |||
96f5c2b488 | |||
09ab9779aa | |||
97ed0a3a8f | |||
ec6d225dc6 | |||
800a332691 | |||
45eae17561 | |||
092365cab2 | |||
c98c2f101d | |||
8e86634a41 | |||
7424e5b707 | |||
5175c937a9 | |||
2692a567ab | |||
2de971fb52 | |||
403b2e8cd9 | |||
22141e723a | |||
419758125e | |||
6db307c681 | |||
cdec6d2f98 | |||
18a6dc9719 | |||
fb92d7858a | |||
b8e005bdd5 | |||
6568489b27 | |||
3ff3e14fc2 | |||
092591f4ec | |||
895a3a1dcc | |||
dc4ce3f8c7 | |||
7e3b9d5065 | |||
e58ffc9fd4 | |||
0d029988c3 | |||
6f01989a4b | |||
19aeaec406 | |||
b4d1241a9e | |||
fef4aa0e86 | |||
766266b147 | |||
93410c07bb | |||
dc27a782b0 | |||
b633df73c0 | |||
41d1200df7 | |||
c9a62fba9a | |||
c9598fa831 | |||
9965c31b2d | |||
43de6425b2 | |||
b98cdf3e0b | |||
26c11ebaed | |||
c9c3de1d04 | |||
9b29b56982 | |||
8956fd7ce0 | |||
f1f94ea3e0 | |||
94b433aead | |||
5623d54114 | |||
9133a2f848 | |||
70324c3ecc | |||
5613d21324 | |||
9036bf3f6b | |||
94991d2df3 | |||
76f23946f0 | |||
862e856913 | |||
cf3de34cb6 | |||
81de79c725 | |||
228b6f1baa | |||
8e395cfd4e | |||
6d6123df9b | |||
321a49156c | |||
e8a47e17fb | |||
9df27ea168 | |||
cb6b7c4f48 | |||
ca623e6b18 | |||
8b6e02136e | |||
6a0493a578 | |||
bf2a437a82 | |||
ac949e3f5e | |||
7483251393 | |||
d690b7d861 | |||
829695d690 |
12
.mtn-ignore
@ -39,9 +39,13 @@ signing.properties
|
||||
build
|
||||
|
||||
# I2P-specific ignores
|
||||
^res/raw/blocklist_txt
|
||||
^res/raw/certificates_zip
|
||||
^res/raw/hosts_txt
|
||||
^res/raw/license_
|
||||
^app/src/main/res/drawable/i2plogo.png
|
||||
^app/src/main/res/raw/blocklist_txt
|
||||
^app/src/main/res/raw/hosts_txt
|
||||
^app/src/main/res/raw/license_
|
||||
^app/src/main/res/raw/certificates_zip
|
||||
^app/src/main/assets/themes/console/images
|
||||
^app/src/main/assets/themes/console/light/console.css
|
||||
^app/src/main/assets/themes/console/light/images/header.png
|
||||
^scripts/build.number
|
||||
^scripts/version.properties
|
||||
|
27
CHANGELOG
Normal file
@ -0,0 +1,27 @@
|
||||
0.9.18
|
||||
* I2P can start automatically when phone boots (configure in Setting)
|
||||
* Updated browser configuration guides for Orfox and Firefox
|
||||
* Tunnels for postman's mail server added to defaults (for new installs)
|
||||
* Settings options for configuring UDP and TCP ports
|
||||
* Bug fixes and translation updates
|
||||
|
||||
0.9.17.1 /2014-12-14 / cd8bb5e3ac4238efac12179c78c4fa517fcaabec
|
||||
* Fixed crashes in addressbook and netDb status page
|
||||
* Fixed crash when opening an IRC client tunnel
|
||||
* Updated translations
|
||||
|
||||
0.9.17 / 2014-12-01 / bcf947f433876f643e0f6dff81aac88848b797d3
|
||||
* Migrated to the new Material design from Android Lollipop
|
||||
* Added a browser configuration guide
|
||||
* Improved the help screen
|
||||
* Upgraded the I2P router to 0.9.17
|
||||
* Various bug fixes and translation updates
|
||||
|
||||
0.9.15.1 / 2014-10-16 / 9cc4e80134cf9becb3838ed3cc78e0bed1363165
|
||||
* Fixed a configuration bug
|
||||
|
||||
0.9.15 / 2014-10-16 / 9b51f78b791c28a580d57f1a5019c94493de6231
|
||||
* Upgraded the I2P router to 0.9.15
|
||||
* Added a help screen with some basic information
|
||||
* Logs can now be copied to the clipboard
|
||||
* Various user interface improvements and fixes
|
22
README.md
@ -7,9 +7,10 @@
|
||||
- Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
- Apache Ant 1.8.0 or higher
|
||||
- I2P source
|
||||
- Android SDK (tested with Rev 22.6.4 and platform-tools version 19.1)
|
||||
- Android SDK for API 21
|
||||
- Android Build Tools 21.0.2
|
||||
- Android Support Repository
|
||||
- Gradle 1.12
|
||||
- Gradle 2.1
|
||||
|
||||
### Gradle
|
||||
|
||||
@ -86,24 +87,15 @@ systemProp.socksProxyPort=9150
|
||||
|
||||
## Client library
|
||||
|
||||
### "Uploading" to a local file repository (to use a local build of the library in a project)
|
||||
### "Uploading" to the local Maven repository (to use a local build of the library in a project)
|
||||
|
||||
1. Add the following line to your `~/.gradle/gradle.properties`:
|
||||
1. `gradle :client:installArchives`
|
||||
|
||||
```
|
||||
localFileRepoDir=/path/to/local/file/repo
|
||||
```
|
||||
|
||||
2. `gradle :client:uploadArchives`
|
||||
|
||||
3. Add the resulting directory to your project as a repository. For Gradle projects, add the following above any existing repositories (so it is checked first):
|
||||
2. Add the local Maven repository to your project. For Gradle projects, add the following above any existing repositories (so it is checked first):
|
||||
|
||||
```
|
||||
repositories {
|
||||
flatDir {
|
||||
name 'fileRepo'
|
||||
dirs file('/path/to/local/file/repo')
|
||||
}
|
||||
mavenLocal()
|
||||
}
|
||||
```
|
||||
|
||||
|
80
TODO
@ -1,18 +1,35 @@
|
||||
# Fixes
|
||||
|
||||
- Better addressbook column widths
|
||||
- Better twopane column widths
|
||||
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
|
||||
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
|
||||
- Create tunnel wizard
|
||||
<zzz> in the tunnel create wizard:
|
||||
<zzz> 'this could be the full base 64 destination key, or an i2p url from your address book"
|
||||
<zzz> 'host name' better than 'URL'. Technically speaking, a host name is not a URL
|
||||
<zzz> hmm would be nice if they could be shared-client or have an option
|
||||
<zzz> was setting up email tunnels
|
||||
- I2PTunnel details
|
||||
<zzz> on the i2ptunnel details, it lists both a 'target' and 'access point' for clients, with the same info
|
||||
<zzz> generally we use 'target' for servers and 'access point' for clients, never both
|
||||
- Browser
|
||||
<zzzccc> Bug report: i2p browser treats 302 as an error
|
||||
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page
|
||||
|
||||
# Short-term
|
||||
|
||||
- Graceful shutdown option
|
||||
<zzzccc> Request: graceful shutdown
|
||||
- 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
|
||||
- I2PTunnel
|
||||
- Show all messages somewhere
|
||||
- Improve detail page, expose advanced settings
|
||||
- Add edit page
|
||||
- Progress feedback for addressbook subscriptions reload
|
||||
- Display release notes directly on new router version
|
||||
- Text content
|
||||
- Move help content from release notes to help page
|
||||
- Rewrite release notes to be release-specific
|
||||
- Fill out help page
|
||||
- Fill out help pages
|
||||
- Rewrite release notes to be release-specific
|
||||
- Fix release notes UI, either make back button use clear or add buttons
|
||||
- NetDB tablet view fixes
|
||||
- Refresh detail fragment when changing tab
|
||||
@ -20,20 +37,63 @@
|
||||
- Create nav history when viewing RI from LS
|
||||
- Include GeoIP db for country info
|
||||
- Maybe change router-off mechanic for various pages? Enable as they become available?
|
||||
- Add "copy (error) log" option
|
||||
|
||||
# Medium-term
|
||||
|
||||
- Network profiles
|
||||
- User selects profile in settings
|
||||
- Change network participation etc. based on profile
|
||||
- Also look at connection type: Connectivity.isConnectionFast()
|
||||
- Expose log level overrides
|
||||
- Improve graphs
|
||||
- Show time on bottom axis
|
||||
- Show fixed x range, not only available data
|
||||
- Think about pan/zoom
|
||||
- How to persist data across restarts?
|
||||
- I2PTunnel
|
||||
- Show all messages somewhere
|
||||
- Improve detail page, expose advanced settings
|
||||
- Add edit page
|
||||
|
||||
# Silent Store approval checks to confirm/implement
|
||||
|
||||
- Known Vulnerabilities
|
||||
- Apps will be tested to ensure that they are not susceptible to known
|
||||
publicly disclosed vulnerabilities. For example:
|
||||
- Heartbleed
|
||||
- Poodle
|
||||
- MasterKey
|
||||
- Common Path Traversal attacks
|
||||
- Common SQL Injection attacks
|
||||
- Network Security Protocols
|
||||
- All Apps that require transmission of data from the App to a system that
|
||||
does not exist on the device must use, at a minimum, TLS1.1 standards.
|
||||
However, Blackphone would prefer the usage of TLS1.2.
|
||||
- Apps must not use algorithms for cryptographic purposes that are considered
|
||||
obsolete or outdated i.e. MD5, SHA1, RC4, DES, or any encryption algorithm
|
||||
that is weaker than AES128.
|
||||
- Transport Layer Protection
|
||||
- All network communication should be encrypted
|
||||
- Not vulnerable to SSl Strip
|
||||
- Data Leakage
|
||||
- No storage of sensitive data outside of application sandbox
|
||||
- Files should not be created with MODE_WORLD_READABLE or MODE_WORLD_WRITABLE
|
||||
- Copy & Paste will be evaluated on a case by case basis
|
||||
- App logs should not contain sensitive information
|
||||
- Authentication and Authorization
|
||||
- Validate that authentication credentials are not stored on the device
|
||||
- Must use an approved password-based key derivation function ie. PBKDF2, scrypt
|
||||
- Data-at-rest Encryption
|
||||
- Must use at a minimum AES128 with modes CCM or GCM
|
||||
- Should not store the encryption key on the file system
|
||||
- Permission Checks
|
||||
- The App must function with all permissions disabled
|
||||
- Apps must not hard crash if a permission is disabled
|
||||
- Apps should ask users to enable permissions that are disabled if needed to
|
||||
function properly and explain why the permission is necessary
|
||||
- Privacy Policy
|
||||
- Apps must have a privacy policy that details how customer data is used,
|
||||
stored, shared, etc...
|
||||
- Apps must be configured with the customer opted out by default
|
||||
- App logs should not contain PII
|
||||
- Error Handling
|
||||
- Apps should follow best-practices for error handling and logging
|
||||
|
||||
# Long-term
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
apply plugin: 'android'
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION)
|
||||
buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION
|
||||
defaultConfig {
|
||||
versionCode 4745226
|
||||
versionName '0.9.18'
|
||||
minSdkVersion 9
|
||||
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
|
||||
}
|
||||
@ -14,9 +16,17 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
runProguard false
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix '.debug'
|
||||
versionNameSuffix '-DEBUG'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
@ -37,15 +47,19 @@ android {
|
||||
dependencies {
|
||||
compile project(':routerjars')
|
||||
compile project(':client')
|
||||
compile 'com.android.support:support-v4:19.1.0'
|
||||
compile 'com.android.support:appcompat-v7:19.1.0'
|
||||
compile files('libs/androidplot-core-0.6.0.jar')
|
||||
compile 'com.android.support:support-v4:21.0.3'
|
||||
compile 'com.android.support:appcompat-v7:21.0.3'
|
||||
compile 'com.android.support:recyclerview-v7:21.0.3'
|
||||
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0'
|
||||
compile files('libs/androidplot-core-0.6.1.jar')
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:support-v4:3f40fa7b3a4ead01ce15dce9453b061646e7fe2e7c51cb75ca01ee1e77037f3f',
|
||||
'com.android.support:appcompat-v7:9df7637c5219202ddbbbf0924c2d5a9e6d64379166795a89d8f75d1e3f3372df',
|
||||
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
|
||||
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
|
||||
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
|
||||
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab',
|
||||
]
|
||||
}
|
||||
|
||||
@ -71,15 +85,17 @@ task certificatesZip(type: Zip) {
|
||||
from files('' + i2pbase + '/installer/resources/certificates')
|
||||
}
|
||||
task copyI2PResources(type: Copy) {
|
||||
into 'src/main'
|
||||
into('res/drawable') {
|
||||
// Force this to always run: Copy only detects source changes, not if missing in destination
|
||||
outputs.upToDateWhen { false }
|
||||
into 'src/main/res'
|
||||
into('drawable') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||
}
|
||||
into('res/raw') {
|
||||
from(i2pbase + '/installer/resources/blocklist.txt') { rename {'blocklist_txt' } }
|
||||
from(i2pbase + '/installer/resources/hosts.txt') { rename {'hosts_txt' } }
|
||||
from('../LICENSE.txt') { rename {'license_app_txt' } }
|
||||
from('../licenses/LICENSE-Apache2.0.txt') { rename {'license_apache20_txt' } }
|
||||
into('raw') {
|
||||
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
|
||||
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
|
||||
from('../LICENSE.txt') { rename { 'license_app_txt' } }
|
||||
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
|
||||
from(i2pbase + '/licenses') {
|
||||
include { elem ->
|
||||
elem.name in [
|
||||
@ -105,19 +121,28 @@ task copyI2PResources(type: Copy) {
|
||||
}
|
||||
from certificatesZip
|
||||
}
|
||||
// For peers WebView
|
||||
into('assets/themes/console/images') {
|
||||
}
|
||||
// For peers WebView
|
||||
task copyI2PAssets(type: Copy) {
|
||||
// Force this to always run: Copy only detects source changes, not if missing in destination
|
||||
outputs.upToDateWhen { false }
|
||||
into 'src/main/assets/themes/console'
|
||||
into('images') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
|
||||
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
|
||||
}
|
||||
into ('assets/themes/console/light') {
|
||||
into('light') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
|
||||
}
|
||||
into('assets/themes/console/light/images') {
|
||||
into('light/images') {
|
||||
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
|
||||
}
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyI2PResources
|
||||
preBuild.dependsOn copyI2PAssets
|
||||
|
||||
task cleanI2PResources(type: Delete) {
|
||||
delete file('src/main/res/drawable/i2plogo.png')
|
||||
delete fileTree('src/main/res/raw') {
|
||||
@ -126,12 +151,15 @@ task cleanI2PResources(type: Delete) {
|
||||
include 'license_*'
|
||||
include 'certificates_zip'
|
||||
}
|
||||
}
|
||||
task cleanI2PAssets(type: Delete) {
|
||||
delete fileTree('src/main/assets/themes/console/images')
|
||||
delete file('src/main/assets/themes/console/light/console.css')
|
||||
delete file('src/main/assets/themes/console/light/images/header.png')
|
||||
}
|
||||
preBuild.dependsOn copyI2PResources
|
||||
|
||||
clean.dependsOn cleanI2PResources
|
||||
clean.dependsOn cleanI2PAssets
|
||||
|
||||
props = new Properties()
|
||||
propFile = new File(project.rootDir, 'signing.properties')
|
||||
|
BIN
app/libs/androidplot-core-0.6.1.jar
Normal file
4
app/src/debug/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">I2P DEBUG</string>
|
||||
</resources>
|
37
app/src/debug/res/xml/settings_headers.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_bandwidth_net">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="net" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/label_graphs">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="graphs" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_logging">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="logging" />
|
||||
</header>
|
||||
<header
|
||||
android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</header>
|
||||
<header
|
||||
android:fragment="net.i2p.android.router.SettingsActivity$SettingsFragment"
|
||||
android:title="@string/settings_label_advanced">
|
||||
<extra
|
||||
android:name="settings"
|
||||
android:value="advanced" />
|
||||
</header>
|
||||
</preference-headers>
|
32
app/src/debug/res/xml/settings_headers_legacy.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<Preference android:title="@string/settings_label_bandwidth_net">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_NET" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_graphs">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_GRAPHS" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_logging">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_LOGGING" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/label_addressbook">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.addressbook.AddressbookSettingsActivity" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/settings_label_advanced">
|
||||
<intent
|
||||
android:targetPackage="net.i2p.android.debug"
|
||||
android:targetClass="net.i2p.android.router.SettingsActivity"
|
||||
android:action="net.i2p.android.router.PREFS_ADVANCED" />
|
||||
</Preference>
|
||||
</PreferenceScreen>
|
@ -1,21 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.i2p.android.router"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="4745219"
|
||||
android:versionName="0.9.14.1-rc3">
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="9"
|
||||
android:targetSdkVersion="19" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat">
|
||||
android:theme="@style/Theme.I2P">
|
||||
<service
|
||||
android:name=".service.RouterService"
|
||||
android:icon="@drawable/ic_launcher_itoopie"
|
||||
@ -26,7 +21,12 @@
|
||||
</service>
|
||||
<provider
|
||||
android:name=".provider.CacheProvider"
|
||||
android:authorities="net.i2p.android" />
|
||||
android:authorities="${applicationId}.provider" />
|
||||
<receiver android:name=".receiver.OnBootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@ -45,15 +45,23 @@
|
||||
<activity
|
||||
android:name=".NewsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="I2P News"
|
||||
android:label="@string/label_news"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:label="Help"
|
||||
android:name="net.i2p.android.help.HelpActivity"
|
||||
android:label="@string/menu_help"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.help.BrowserConfigActivity"
|
||||
android:label="@string/label_browser_configuration"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@ -61,11 +69,11 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".LicenseActivity"
|
||||
android:label="I2P License Information"
|
||||
android:parentActivityName=".HelpActivity">
|
||||
android:label="@string/label_licenses"
|
||||
android:parentActivityName="net.i2p.android.help.HelpActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.HelpActivity" />
|
||||
android:value="net.i2p.android.help.HelpActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".web.WebActivity"
|
||||
@ -86,7 +94,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="I2P Settings"
|
||||
android:label="@string/menu_settings"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@ -94,7 +102,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".addressbook.AddressbookSettingsActivity"
|
||||
android:label="I2P Addressbook Settings"
|
||||
android:label="Addressbook Settings"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".addressbook.AddressbookActivity">
|
||||
<meta-data
|
||||
@ -103,7 +111,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".addressbook.AddressbookActivity"
|
||||
android:label="Addressbook"
|
||||
android:label="@string/label_addressbook"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
@ -127,7 +135,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
|
||||
android:label="I2PTunnel"
|
||||
android:label="@string/label_i2ptunnel"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
@ -140,7 +148,7 @@
|
||||
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
|
||||
android:value="net.i2p.android.i2ptunnel.TunnelListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"
|
||||
|
83
app/src/main/java/net/i2p/android/help/Browser.java
Normal file
@ -0,0 +1,83 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public class Browser implements Comparable<Browser> {
|
||||
public final String packageName;
|
||||
public final CharSequence label;
|
||||
public final Drawable icon;
|
||||
public final boolean isInstalled;
|
||||
public final boolean isKnown;
|
||||
public final boolean isSupported;
|
||||
public final boolean isRecommended;
|
||||
|
||||
/**
|
||||
* A browser that we don't know about.
|
||||
*
|
||||
* @param pm the PackageManager used to find the browser
|
||||
* @param browser the browser
|
||||
*/
|
||||
public Browser(PackageManager pm, ResolveInfo browser) {
|
||||
this(
|
||||
browser.activityInfo.packageName,
|
||||
browser.loadLabel(pm),
|
||||
browser.loadIcon(pm),
|
||||
true, false, false, false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A browser that we know about.
|
||||
*
|
||||
* @param pm the PackageManager used to find the browser
|
||||
* @param browser the browser
|
||||
* @param supported can this browser be used with I2P?
|
||||
*/
|
||||
public Browser(PackageManager pm, ResolveInfo browser, boolean supported, boolean recommended) {
|
||||
this(
|
||||
browser.activityInfo.packageName,
|
||||
browser.loadLabel(pm),
|
||||
browser.loadIcon(pm),
|
||||
true, true, supported, recommended
|
||||
);
|
||||
}
|
||||
|
||||
public Browser(String pn, CharSequence l, Drawable ic, boolean i, boolean k, boolean s, boolean r) {
|
||||
packageName = pn;
|
||||
label = l;
|
||||
icon = ic;
|
||||
isInstalled = i;
|
||||
isKnown = k;
|
||||
isSupported = s;
|
||||
isRecommended = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull Browser browser) {
|
||||
// Sort order: supported -> unknown -> unsupported
|
||||
int a = getOrder(this);
|
||||
int b = getOrder(browser);
|
||||
|
||||
if (a < b)
|
||||
return -1;
|
||||
else if (a > b)
|
||||
return 1;
|
||||
|
||||
return label.toString().compareTo(browser.label.toString());
|
||||
}
|
||||
|
||||
private static int getOrder(Browser browser) {
|
||||
if (browser.isKnown) {
|
||||
if (browser.isRecommended)
|
||||
return 0;
|
||||
else if (browser.isSupported)
|
||||
return 1;
|
||||
else
|
||||
return 3;
|
||||
} else
|
||||
return 2;
|
||||
}
|
||||
}
|
117
app/src/main/java/net/i2p/android/help/BrowserAdapter.java
Normal file
@ -0,0 +1,117 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.net.Uri;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class BrowserAdapter extends RecyclerView.Adapter<BrowserAdapter.ViewHolder> {
|
||||
private Context mCtx;
|
||||
private Browser[] mBrowsers;
|
||||
private OnBrowserSelectedListener mListener;
|
||||
|
||||
// Provide a reference to the views for each data item
|
||||
// Complex data items may need more than one view per item, and
|
||||
// you provide access to all the views for a data item in a view holder
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ImageView mIcon;
|
||||
public TextView mLabel;
|
||||
public ImageView mStatus;
|
||||
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
mIcon = (ImageView) v.findViewById(R.id.browser_icon);
|
||||
mLabel = (TextView) v.findViewById(R.id.browser_label);
|
||||
mStatus = (ImageView) v.findViewById(R.id.browser_status_icon);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface OnBrowserSelectedListener {
|
||||
public void onBrowserSelected(Browser browser);
|
||||
}
|
||||
|
||||
public BrowserAdapter(Context ctx, OnBrowserSelectedListener listener) {
|
||||
mCtx = ctx;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setBrowsers(Browser[] browsers) {
|
||||
mBrowsers = browsers;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mBrowsers = null;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Create new views (invoked by the layout manager)
|
||||
@Override
|
||||
public BrowserAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.listitem_browser, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
final Browser browser = mBrowsers[position];
|
||||
holder.mIcon.setImageDrawable(browser.icon);
|
||||
holder.mLabel.setText(browser.label);
|
||||
|
||||
if (browser.isKnown) {
|
||||
if (browser.isRecommended && browser.isInstalled) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_stars_white_24dp));
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (browser.isSupported && !browser.isInstalled) {
|
||||
holder.mStatus.setImageDrawable(
|
||||
mCtx.getResources().getDrawable(R.drawable.ic_shop_white_24dp));
|
||||
holder.mStatus.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String uriMarket = "market://search?q=pname:" + browser.packageName;
|
||||
Uri uri = Uri.parse(uriMarket);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
mCtx.startActivity(intent);
|
||||
}
|
||||
});
|
||||
holder.mStatus.setVisibility(View.VISIBLE);
|
||||
} else if (!browser.isSupported) {
|
||||
// Make the icon gray-scale to show it is unsupported
|
||||
ColorMatrix matrix = new ColorMatrix();
|
||||
matrix.setSaturation(0);
|
||||
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
|
||||
holder.mIcon.setColorFilter(filter);
|
||||
holder.mLabel.setTextColor(
|
||||
mCtx.getResources().getColor(R.color.primary_text_disabled_material_dark));
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mListener.onBrowserSelected(browser);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Return the size of the dataset (invoked by the layout manager)
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (mBrowsers != null)
|
||||
return mBrowsers.length;
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class BrowserConfigActivity extends ActionBarActivity implements
|
||||
BrowserAdapter.OnBrowserSelectedListener {
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, new BrowserListFragment())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
// BrowserAdapter.OnBrowserSelected
|
||||
|
||||
@Override
|
||||
public void onBrowserSelected(Browser browser) {
|
||||
int file;
|
||||
if (browser.isKnown) {
|
||||
if (browser.isSupported) {
|
||||
// Check for embedded browser
|
||||
if (browser.packageName.startsWith("net.i2p.android"))
|
||||
file = R.raw.help_embedded_browser;
|
||||
else {
|
||||
// Load the configuration guide for this browser
|
||||
try {
|
||||
String name = "help_" + browser.packageName.replace('.', '_');
|
||||
Class res = R.raw.class;
|
||||
Field field = res.getField(name);
|
||||
file = field.getInt(null);
|
||||
} catch (Exception e) {
|
||||
file = R.raw.help_unknown_browser;
|
||||
}
|
||||
}
|
||||
} else
|
||||
file = R.raw.help_unsupported_browser;
|
||||
} else
|
||||
file = R.raw.help_unknown_browser;
|
||||
HelpHtmlFragment configFrag = HelpHtmlFragment.newInstance(file);
|
||||
if (mTwoPane) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, configFrag)
|
||||
.commit();
|
||||
} else {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, configFrag)
|
||||
.addToBackStack("config" + browser.packageName)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
}
|
187
app/src/main/java/net/i2p/android/help/BrowserListFragment.java
Normal file
@ -0,0 +1,187 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.BetterAsyncTaskLoader;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BrowserListFragment extends Fragment implements
|
||||
LoaderManager.LoaderCallbacks<List<Browser>> {
|
||||
private static final int BROWSER_LOADER_ID = 1;
|
||||
|
||||
private BrowserAdapter.OnBrowserSelectedListener mCallback;
|
||||
private BrowserAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mCallback = (BrowserAdapter.OnBrowserSelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnBrowserSelectedListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_help_browsers, container, false);
|
||||
RecyclerView mRecyclerView = (RecyclerView) v.findViewById(R.id.browser_list);
|
||||
|
||||
// use a linear layout manager
|
||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
||||
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||
|
||||
mAdapter = new BrowserAdapter(getActivity(), mCallback);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
getLoaderManager().initLoader(BROWSER_LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
// LoaderManager.LoaderCallbacks<List<Browser>>
|
||||
|
||||
@Override
|
||||
public Loader<List<Browser>> onCreateLoader(int id, Bundle args) {
|
||||
return new BrowserLoader(getActivity());
|
||||
}
|
||||
|
||||
public static class BrowserLoader extends BetterAsyncTaskLoader<List<Browser>> {
|
||||
private List<String> recommended;
|
||||
private List<String> recommendedLabels;
|
||||
private List<String> supported;
|
||||
private List<String> supportedLabels;
|
||||
private List<String> unsupported;
|
||||
|
||||
public BrowserLoader(Context context) {
|
||||
super(context);
|
||||
recommended = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.recommended_browsers));
|
||||
recommendedLabels = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.recommended_browser_labels));
|
||||
supported = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.supported_browsers));
|
||||
supportedLabels = Arrays.asList(
|
||||
getContext().getResources().getStringArray(R.array.supported_browser_labels));
|
||||
unsupported = Arrays.asList(
|
||||
context.getResources().getStringArray(R.array.unsupported_browsers));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Browser> loadInBackground() {
|
||||
List<Browser> browsers = new ArrayList<>();
|
||||
Map<String, String> recommendedMap = new HashMap<>();
|
||||
for (int i = 0; i < recommended.size(); i++) {
|
||||
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
|
||||
}
|
||||
Map<String, String> supportedMap = new HashMap<>();
|
||||
for (int i = 0; i < supported.size(); i++) {
|
||||
supportedMap.put(supported.get(i), supportedLabels.get(i));
|
||||
}
|
||||
|
||||
// Find all installed browsers that listen for ".i2p"
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("http://stats.i2p"));
|
||||
|
||||
final PackageManager pm = getContext().getPackageManager();
|
||||
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
|
||||
|
||||
for (ResolveInfo browser : installedBrowsers) {
|
||||
if (recommended.contains(browser.activityInfo.packageName)) {
|
||||
browsers.add(new Browser(pm, browser, true, true));
|
||||
recommendedMap.remove(browser.activityInfo.packageName);
|
||||
} else if (supported.contains(browser.activityInfo.packageName) ||
|
||||
browser.activityInfo.packageName.startsWith("net.i2p.android")) {
|
||||
browsers.add(new Browser(pm, browser, true, false));
|
||||
supportedMap.remove(browser.activityInfo.packageName);
|
||||
} else if (unsupported.contains(browser.activityInfo.packageName))
|
||||
browsers.add(new Browser(pm, browser, false, false));
|
||||
else
|
||||
browsers.add(new Browser(pm, browser));
|
||||
}
|
||||
|
||||
// Now add the remaining recommended and supported browsers
|
||||
for (Map.Entry<String, String> browser : recommendedMap.entrySet()) {
|
||||
browsers.add(new Browser(browser.getKey(), browser.getValue(),
|
||||
getDrawableForPackage(browser.getKey()),
|
||||
false, true, true, true));
|
||||
}
|
||||
for (Map.Entry<String, String> browser : supportedMap.entrySet()) {
|
||||
browsers.add(new Browser(browser.getKey(), browser.getValue(),
|
||||
getDrawableForPackage(browser.getKey()),
|
||||
false, true, true, false));
|
||||
}
|
||||
|
||||
Collections.sort(browsers);
|
||||
return browsers;
|
||||
}
|
||||
private Drawable getDrawableForPackage(String packageName) {
|
||||
try {
|
||||
String name = "icon_" + packageName.replace('.', '_');
|
||||
Class res = R.drawable.class;
|
||||
Field field = res.getField(name);
|
||||
int drawable = field.getInt(null);
|
||||
return getContext().getResources().getDrawable(drawable);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartMonitoring() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopMonitoring() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseResources(List<Browser> data) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<Browser>> listLoader, List<Browser> browsers) {
|
||||
if (listLoader.getId() == BROWSER_LOADER_ID)
|
||||
mAdapter.setBrowsers(browsers.toArray(new Browser[browsers.size()]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<Browser>> listLoader) {
|
||||
if (listLoader.getId() == BROWSER_LOADER_ID)
|
||||
mAdapter.clear();
|
||||
}
|
||||
}
|
155
app/src/main/java/net/i2p/android/help/HelpActivity.java
Normal file
@ -0,0 +1,155 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import net.i2p.android.router.LicenseActivity;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
|
||||
public class HelpActivity extends ActionBarActivity implements
|
||||
HelpListFragment.OnEntrySelectedListener {
|
||||
public static final String CATEGORY = "help_category";
|
||||
public static final int CAT_MAIN = 0;
|
||||
public static final int CAT_CONFIGURE_BROWSER = 1;
|
||||
public static final int CAT_ADDRESSBOOK = 2;
|
||||
public static final int CAT_I2PTUNNEL = 3;
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
private int mCategory;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, new HelpListFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
mCategory = getIntent().getIntExtra(CATEGORY, -1);
|
||||
if (mCategory >= 0) {
|
||||
showCategory(mCategory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if (mCategory >= 0) {
|
||||
onBackPressed();
|
||||
} else {
|
||||
Intent upIntent = NavUtils.getParentActivityIntent(this);
|
||||
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
|
||||
// This activity is NOT part of this app's task, so create a new task
|
||||
// when navigating up, with a synthesized back stack.
|
||||
TaskStackBuilder.create(this)
|
||||
// Add all of this activity's parents to the back stack
|
||||
.addNextIntentWithParentStack(upIntent)
|
||||
// Navigate up to the closest parent
|
||||
.startActivities();
|
||||
} else {
|
||||
// This activity is part of this app's task, so simply
|
||||
// navigate up to the logical parent activity.
|
||||
NavUtils.navigateUpTo(this, upIntent);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case R.id.menu_help_licenses:
|
||||
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
|
||||
startActivity(lic);
|
||||
return true;
|
||||
case R.id.menu_help_release_notes:
|
||||
TextResourceDialog dialog = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
|
||||
getResources().getString(R.string.label_release_notes));
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getSupportFragmentManager(), "release_notes");
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
if (mCategory >= 0)
|
||||
mCategory = -1;
|
||||
}
|
||||
|
||||
// HelpListFragment.OnEntrySelectedListener
|
||||
|
||||
@Override
|
||||
public void onEntrySelected(int entry) {
|
||||
if (entry == CAT_CONFIGURE_BROWSER) {
|
||||
Intent i = new Intent(this, BrowserConfigActivity.class);
|
||||
startActivity(i);
|
||||
} else {
|
||||
mCategory = entry;
|
||||
showCategory(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void showCategory(int category) {
|
||||
int file;
|
||||
switch (category) {
|
||||
case CAT_ADDRESSBOOK:
|
||||
file = R.raw.help_addressbook;
|
||||
break;
|
||||
|
||||
case CAT_I2PTUNNEL:
|
||||
file = R.raw.help_i2ptunnel;
|
||||
break;
|
||||
|
||||
case CAT_MAIN:
|
||||
default:
|
||||
file = R.raw.help_main;
|
||||
break;
|
||||
}
|
||||
HelpHtmlFragment f = HelpHtmlFragment.newInstance(file);
|
||||
if (mTwoPane) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, f).commit();
|
||||
} else {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f)
|
||||
.addToBackStack("help" + category)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
}
|
36
app/src/main/java/net/i2p/android/help/HelpHtmlFragment.java
Normal file
@ -0,0 +1,36 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
|
||||
public class HelpHtmlFragment extends Fragment {
|
||||
public static final String ARG_HTML_FILE = "htmlFile";
|
||||
|
||||
static HelpHtmlFragment newInstance(int htmlFile) {
|
||||
HelpHtmlFragment f = new HelpHtmlFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_HTML_FILE, htmlFile);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
ScrollView scroller = new ScrollView(getActivity());
|
||||
HtmlTextView text = new HtmlTextView(getActivity());
|
||||
scroller.addView(text);
|
||||
int padH = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
|
||||
int padV = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);
|
||||
text.setPadding(padH, padV, padH, padV);
|
||||
text.setHtmlFromRawResource(getActivity(), getArguments().getInt(ARG_HTML_FILE), true);
|
||||
return scroller;
|
||||
}
|
||||
}
|
47
app/src/main/java/net/i2p/android/help/HelpListFragment.java
Normal file
@ -0,0 +1,47 @@
|
||||
package net.i2p.android.help;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class HelpListFragment extends ListFragment {
|
||||
OnEntrySelectedListener mEntrySelectedCallback;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnEntrySelectedListener {
|
||||
public void onEntrySelected(int entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mEntrySelectedCallback = (OnEntrySelectedListener) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement OnEntrySelectedListener");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setListAdapter(ArrayAdapter.createFromResource(getActivity(),
|
||||
R.array.help_categories, R.layout.listitem_text));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView parent, View view, int pos, long id) {
|
||||
super.onListItemClick(parent, view, pos, id);
|
||||
mEntrySelectedCallback.onEntrySelected(pos);
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class TunnelDetailActivity extends I2PActivityBase implements
|
||||
TunnelDetailFragment.OnTunnelDeletedListener {
|
||||
@ -11,6 +12,8 @@ public class TunnelDetailActivity extends I2PActivityBase implements
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
|
||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||
|
@ -1,15 +1,11 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
@ -22,6 +18,12 @@ import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelDetailFragment extends Fragment {
|
||||
public static final String TUNNEL_ID = "tunnel_id";
|
||||
|
||||
@ -96,11 +98,14 @@ 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());
|
||||
|
||||
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
|
||||
targetIfacePort.setText(mTunnel.getIfacePort());
|
||||
targetIfacePort.setText(mTunnel.getTunnelLink(false));
|
||||
|
||||
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
|
||||
accessIfacePort.setText(mTunnel.getIfacePort());
|
||||
accessIfacePort.setText(mTunnel.getTunnelLink(false));
|
||||
|
||||
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
|
||||
autoStart.setChecked(mTunnel.startAutomatically());
|
||||
@ -162,6 +167,7 @@ public class TunnelDetailFragment extends Fragment {
|
||||
return true;
|
||||
case R.id.action_delete_tunnel:
|
||||
DialogFragment dg = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
|
@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.widget.Toast;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelUtil;
|
||||
@ -90,6 +91,16 @@ public class TunnelEntry {
|
||||
else return NOT_RUNNING;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
switch (getStatus()) {
|
||||
case STANDBY:
|
||||
case RUNNING:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClient() {
|
||||
return TunnelUtil.isClient(mController.getType());
|
||||
}
|
||||
@ -100,18 +111,42 @@ public class TunnelEntry {
|
||||
return Boolean.parseBoolean(mController.getSharedClient());
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to see if it is okay to linkify getClientLink()
|
||||
* @return true if getClientLink() can be linkified, false otherwise.
|
||||
*/
|
||||
public boolean isClientLinkValid() {
|
||||
return ("ircclient".equals(mController.getType())) &&
|
||||
mController.getListenOnInterface() != null &&
|
||||
mController.getListenPort() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return valid host:port only if isClientLinkValid() is true
|
||||
*/
|
||||
public String getClientLink(boolean linkify) {
|
||||
String host = getClientInterface();
|
||||
String port = getClientPort();
|
||||
String link = host + ":" + port;
|
||||
if (linkify) {
|
||||
if ("ircclient".equals(mController.getType()))
|
||||
link = "irc://" + link;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getClientInterface() {
|
||||
String rv;
|
||||
if ("streamrclient".equals(mController.getType()))
|
||||
return mController.getTargetHost();
|
||||
rv = mController.getTargetHost();
|
||||
else
|
||||
return mController.getListenOnInterface();
|
||||
rv = mController.getListenOnInterface();
|
||||
return rv != null ? rv : "";
|
||||
}
|
||||
|
||||
public String getClientPort() {
|
||||
String rv = mController.getListenPort();
|
||||
if (rv != null)
|
||||
return rv;
|
||||
return "";
|
||||
return rv != null ? rv : "";
|
||||
}
|
||||
|
||||
public String getClientDestination() {
|
||||
@ -128,10 +163,10 @@ public class TunnelEntry {
|
||||
/* Server tunnel data */
|
||||
|
||||
/**
|
||||
* Call this to see if it is okay to linkify getServerTarget()
|
||||
* @return true if getServerTarget() can be linkified, false otherwise.
|
||||
* Call this to see if it is okay to linkify getServerLink()
|
||||
* @return true if getServerLink() can be linkified, false otherwise.
|
||||
*/
|
||||
public boolean isServerTargetLinkValid() {
|
||||
public boolean isServerLinkValid() {
|
||||
return ("httpserver".equals(mController.getType()) ||
|
||||
"httpbidirserver".equals(mController.getType())) &&
|
||||
mController.getTargetHost() != null &&
|
||||
@ -139,9 +174,9 @@ public class TunnelEntry {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return valid host:port only if isServerTargetLinkValid() is true
|
||||
* @return valid host:port only if isServerLinkValid() is true
|
||||
*/
|
||||
public String getServerTarget() {
|
||||
public String getServerLink(boolean linkify) {
|
||||
String host;
|
||||
if ("streamrserver".equals(getInternalType()))
|
||||
host = mController.getListenOnInterface();
|
||||
@ -152,7 +187,13 @@ public class TunnelEntry {
|
||||
if (port == null) port = "";
|
||||
if (host.indexOf(':') >= 0)
|
||||
host = '[' + host + ']';
|
||||
return host + ":" + port;
|
||||
String link = host + ":" + port;
|
||||
if (linkify) {
|
||||
if ("httpserver".equals(mController.getType()) ||
|
||||
"httpbidirserver".equals(mController.getType()))
|
||||
link = "http://" + link;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getDestinationBase64() {
|
||||
@ -183,18 +224,25 @@ public class TunnelEntry {
|
||||
|
||||
/* Other output formats */
|
||||
|
||||
public String getIfacePort() {
|
||||
if (isClient()) {
|
||||
String host;
|
||||
if ("streamrclient".equals(getInternalType()))
|
||||
host = mController.getTargetHost();
|
||||
else
|
||||
host = mController.getListenOnInterface();
|
||||
String port = mController.getListenPort();
|
||||
if (host == null) host = "";
|
||||
if (port == null) port = "";
|
||||
return host + ":" + port;
|
||||
} else return getServerTarget();
|
||||
public boolean isTunnelLinkValid() {
|
||||
if (isClient()) return isClientLinkValid();
|
||||
else return isServerLinkValid();
|
||||
}
|
||||
|
||||
public String getTunnelLink(boolean linkify) {
|
||||
if (isClient()) return getClientLink(linkify);
|
||||
else return getServerLink(linkify);
|
||||
}
|
||||
|
||||
public Uri getRecommendedAppForTunnel() {
|
||||
int resId = 0;
|
||||
if ("ircclient".equals(mController.getType()))
|
||||
resId = R.string.market_irc;
|
||||
|
||||
if (resId > 0)
|
||||
return Uri.parse(mContext.getString(resId));
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
@ -209,16 +257,29 @@ public class TunnelEntry {
|
||||
public Drawable getStatusIcon() {
|
||||
switch (getStatus()) {
|
||||
case STANDBY:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.ic_schedule_black_24dp);
|
||||
case STARTING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.local_inprogress);
|
||||
case RUNNING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.local_up);
|
||||
case NOT_RUNNING:
|
||||
default:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.local_down);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getStatusBackground() {
|
||||
switch (getStatus()) {
|
||||
case STANDBY:
|
||||
case STARTING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.tunnel_yellow);
|
||||
case RUNNING:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.tunnel_green);
|
||||
case NOT_RUNNING:
|
||||
default:
|
||||
return mContext.getResources()
|
||||
.getDrawable(R.drawable.tunnel_red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -11,6 +14,10 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
@ -31,22 +38,56 @@ public class TunnelEntryAdapter extends ArrayAdapter<TunnelEntry> {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = mInflater.inflate(R.layout.listitem_i2ptunnel, parent, false);
|
||||
TunnelEntry tunnel = getItem(position);
|
||||
final TunnelEntry tunnel = getItem(position);
|
||||
|
||||
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||
status.setImageDrawable(tunnel.getStatusIcon());
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||
status.setBackgroundDrawable(tunnel.getStatusBackground());
|
||||
else
|
||||
status.setBackground(tunnel.getStatusBackground());
|
||||
|
||||
TextView name = (TextView) v.findViewById(R.id.tunnel_name);
|
||||
name.setText(tunnel.getName());
|
||||
|
||||
TextView type = (TextView) v.findViewById(R.id.tunnel_type);
|
||||
type.setText(tunnel.getType());
|
||||
TextView type = (TextView) v.findViewById(R.id.tunnel_description);
|
||||
type.setText(tunnel.getDescription());
|
||||
|
||||
TextView ifacePort = (TextView) v.findViewById(R.id.tunnel_interface_port);
|
||||
ifacePort.setText(tunnel.getIfacePort());
|
||||
ifacePort.setText(tunnel.getTunnelLink(false));
|
||||
|
||||
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
|
||||
details.setText(tunnel.getDetails());
|
||||
|
||||
ImageView status = (ImageView) v.findViewById(R.id.tunnel_status);
|
||||
status.setImageDrawable(tunnel.getStatusIcon());
|
||||
if (tunnel.isRunning() && tunnel.isTunnelLinkValid()) {
|
||||
View open = v.findViewById(R.id.tunnel_open);
|
||||
open.setVisibility(View.VISIBLE);
|
||||
open.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(tunnel.getTunnelLink(true)));
|
||||
try {
|
||||
getContext().startActivity(i);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(R.string.install_recommended_app)
|
||||
.setMessage(R.string.app_needed_for_this_tunnel_type)
|
||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Uri uri = tunnel.getRecommendedAppForTunnel();
|
||||
if (uri != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(net.i2p.android.lib.client.R.string.no, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
|
||||
private TunnelControllerGroup mGroup;
|
||||
private boolean mClientTunnels;
|
||||
@ -26,7 +26,7 @@ public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
|
||||
|
||||
@Override
|
||||
public List<TunnelEntry> loadInBackground() {
|
||||
List<TunnelEntry> ret = new ArrayList<TunnelEntry>();
|
||||
List<TunnelEntry> ret = new ArrayList<>();
|
||||
List<TunnelController> controllers = mGroup.getControllers();
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);
|
||||
|
@ -1,11 +1,14 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBar.Tab;
|
||||
|
||||
public class TunnelListActivity extends I2PActivityBase implements
|
||||
TunnelListFragment.OnTunnelSelectedListener,
|
||||
@ -16,7 +19,10 @@ public class TunnelListActivity extends I2PActivityBase implements
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_TAB = "selected_tab";
|
||||
private static final String SELECTED_PAGE = "selected_page";
|
||||
private static final int PAGE_CLIENT = 0;
|
||||
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
@ -27,34 +33,22 @@ public class TunnelListActivity extends I2PActivityBase implements
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set up action bar for tabs
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
// Client tunnels tab
|
||||
TunnelListFragment cf = new TunnelListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, true);
|
||||
cf.setArguments(args);
|
||||
Tab tab = actionBar.newTab()
|
||||
.setText(R.string.label_i2ptunnel_client)
|
||||
.setTabListener(new TabListener(cf));
|
||||
actionBar.addTab(tab);
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.i2ptunnel_pages, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
// Server tunnels tab
|
||||
TunnelListFragment sf = new TunnelListFragment();
|
||||
args = new Bundle();
|
||||
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, false);
|
||||
sf.setArguments(args);
|
||||
tab = actionBar.newTab()
|
||||
.setText(R.string.label_i2ptunnel_server)
|
||||
.setTabListener(new TabListener(sf));
|
||||
actionBar.addTab(tab);
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectPage(i);
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_TAB);
|
||||
actionBar.setSelectedNavigationItem(selected);
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
@ -62,19 +56,34 @@ public class TunnelListActivity extends I2PActivityBase implements
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
cf.setActivateOnItemClick(true);
|
||||
sf.setActivateOnItemClick(true);
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_PAGE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectPage(PAGE_CLIENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_TAB,
|
||||
getSupportActionBar().getSelectedNavigationIndex());
|
||||
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private void selectPage(int page) {
|
||||
TunnelListFragment f = new TunnelListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(TunnelListFragment.SHOW_CLIENT_TUNNELS, page == PAGE_CLIENT);
|
||||
f.setArguments(args);
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
f.setActivateOnItemClick(true);
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f).commit();
|
||||
}
|
||||
|
||||
// TunnelListFragment.OnTunnelSelectedListener
|
||||
@ -86,7 +95,7 @@ public class TunnelListActivity extends I2PActivityBase implements
|
||||
// fragment transaction.
|
||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
@ -105,11 +114,11 @@ public class TunnelListActivity extends I2PActivityBase implements
|
||||
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(
|
||||
(tunnelId > 0 ? tunnelId - 1 : 0));
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
} else {
|
||||
TunnelDetailFragment detailFrag = (TunnelDetailFragment) getSupportFragmentManager().findFragmentById(R.id.detail_fragment);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.remove(detailFrag).commit();
|
||||
.remove(detailFrag).commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,32 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
||||
import net.i2p.android.router.HelpActivity;
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.router.RouterContext;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.android.help.HelpActivity;
|
||||
import net.i2p.android.i2ptunnel.util.TunnelConfig;
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TunnelListFragment extends ListFragment implements
|
||||
I2PFragmentBase.RouterContextUser,
|
||||
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
|
||||
@ -50,6 +55,8 @@ public class TunnelListFragment extends ListFragment implements
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
private boolean mActivateOnItemClick = false;
|
||||
|
||||
private ImageButton mNewTunnel;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnTunnelSelectedListener {
|
||||
public void onTunnelSelected(int tunnelId);
|
||||
@ -85,6 +92,27 @@ public class TunnelListFragment extends ListFragment implements
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Create the list fragment's content view by calling the super method
|
||||
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
|
||||
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
|
||||
listContainer.addView(listFragmentView);
|
||||
|
||||
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||
mNewTunnel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
|
||||
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@ -162,7 +190,7 @@ public class TunnelListFragment extends ListFragment implements
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
|
||||
if (getRouterContext() == null) {
|
||||
menu.findItem(R.id.action_add_tunnel).setVisible(false);
|
||||
mNewTunnel.setVisibility(View.GONE);
|
||||
menu.findItem(R.id.action_start_all_tunnels).setVisible(false);
|
||||
menu.findItem(R.id.action_stop_all_tunnels).setVisible(false);
|
||||
menu.findItem(R.id.action_restart_all_tunnels).setVisible(false);
|
||||
@ -174,27 +202,22 @@ public class TunnelListFragment extends ListFragment implements
|
||||
// Handle presses on the action bar items
|
||||
List<String> msgs;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_add_tunnel:
|
||||
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
|
||||
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
|
||||
return true;
|
||||
case R.id.action_start_all_tunnels:
|
||||
msgs = mGroup.startAllControllers();
|
||||
break;
|
||||
case R.id.action_stop_all_tunnels:
|
||||
msgs = mGroup.stopAllControllers();
|
||||
break;
|
||||
case R.id.action_restart_all_tunnels:
|
||||
msgs = mGroup.restartAllControllers();
|
||||
break;
|
||||
// TODO: Enable when Help page finished
|
||||
//case R.id.action_i2ptunnel_help:
|
||||
// Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||
// hi.putExtra(HelpActivity.REFERRER, "i2ptunnel");
|
||||
// startActivity(hi);
|
||||
// return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
case R.id.action_start_all_tunnels:
|
||||
msgs = mGroup.startAllControllers();
|
||||
break;
|
||||
case R.id.action_stop_all_tunnels:
|
||||
msgs = mGroup.stopAllControllers();
|
||||
break;
|
||||
case R.id.action_restart_all_tunnels:
|
||||
msgs = mGroup.restartAllControllers();
|
||||
break;
|
||||
case R.id.action_i2ptunnel_help:
|
||||
Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
// TODO: Do something with the other messages
|
||||
if (msgs.size() > 0)
|
||||
@ -252,7 +275,7 @@ public class TunnelListFragment extends ListFragment implements
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<TunnelEntry>> loader,
|
||||
List<TunnelEntry> data) {
|
||||
List<TunnelEntry> data) {
|
||||
if (loader.getId() == (mClientTunnels ?
|
||||
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
|
||||
mAdapter.setData(data);
|
||||
|
@ -1,16 +1,18 @@
|
||||
package net.i2p.android.i2ptunnel;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||
|
||||
public class TunnelWizardActivity extends AbstractWizardActivity {
|
||||
@Override
|
||||
protected AbstractWizardModel onCreateModel() {
|
||||
@ -20,6 +22,7 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
|
||||
@Override
|
||||
protected DialogFragment onGetFinishWizardDialog() {
|
||||
return new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
|
@ -1,11 +1,5 @@
|
||||
package net.i2p.android.i2ptunnel.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
@ -17,6 +11,12 @@ import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.SecureFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
public abstract class TunnelUtil {
|
||||
public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) {
|
||||
if (tunnel < 0) return null;
|
||||
@ -36,7 +36,7 @@ public abstract class TunnelUtil {
|
||||
TunnelController cur = getController(tcg, tunnelId);
|
||||
|
||||
if (config == null) {
|
||||
List<String> ret = new ArrayList<String>();
|
||||
List<String> ret = new ArrayList<>();
|
||||
ret.add("Invalid params");
|
||||
return ret;
|
||||
}
|
||||
@ -101,7 +101,7 @@ public abstract class TunnelUtil {
|
||||
List<String> msgs;
|
||||
TunnelController cur = getController(tcg, tunnelId);
|
||||
if (cur == null) {
|
||||
msgs = new ArrayList<String>();
|
||||
msgs = new ArrayList<>();
|
||||
msgs.add("Invalid tunnel number");
|
||||
return msgs;
|
||||
}
|
||||
@ -192,32 +192,34 @@ public abstract class TunnelUtil {
|
||||
|
||||
public static String getTypeName(String type, Context context) {
|
||||
Resources res = context.getResources();
|
||||
if ("client".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_client);
|
||||
else if ("httpclient".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_httpclient);
|
||||
else if ("ircclient".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_ircclient);
|
||||
else if ("server".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_server);
|
||||
else if ("httpserver".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_httpserver);
|
||||
else if ("sockstunnel".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_sockstunnel);
|
||||
else if ("socksirctunnel".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
|
||||
else if ("connectclient".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_connectclient);
|
||||
else if ("ircserver".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_ircserver);
|
||||
else if ("streamrclient".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_streamrclient);
|
||||
else if ("streamrserver".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_streamrserver);
|
||||
else if ("httpbidirserver".equals(type))
|
||||
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
|
||||
else
|
||||
return type;
|
||||
switch (type) {
|
||||
case "client":
|
||||
return res.getString(R.string.i2ptunnel_type_client);
|
||||
case "httpclient":
|
||||
return res.getString(R.string.i2ptunnel_type_httpclient);
|
||||
case "ircclient":
|
||||
return res.getString(R.string.i2ptunnel_type_ircclient);
|
||||
case "server":
|
||||
return res.getString(R.string.i2ptunnel_type_server);
|
||||
case "httpserver":
|
||||
return res.getString(R.string.i2ptunnel_type_httpserver);
|
||||
case "sockstunnel":
|
||||
return res.getString(R.string.i2ptunnel_type_sockstunnel);
|
||||
case "socksirctunnel":
|
||||
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
|
||||
case "connectclient":
|
||||
return res.getString(R.string.i2ptunnel_type_connectclient);
|
||||
case "ircserver":
|
||||
return res.getString(R.string.i2ptunnel_type_ircserver);
|
||||
case "streamrclient":
|
||||
return res.getString(R.string.i2ptunnel_type_streamrclient);
|
||||
case "streamrserver":
|
||||
return res.getString(R.string.i2ptunnel_type_streamrserver);
|
||||
case "httpbidirserver":
|
||||
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isClient(String type) {
|
||||
|
@ -1,52 +0,0 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
public class HelpActivity extends I2PActivityBase {
|
||||
public static final String REFERRER = "help_referrer";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
/*if (savedInstanceState == null) {
|
||||
HelpFragment f = new HelpFragment();
|
||||
f.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.main_fragment, f).commit();
|
||||
}*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_help_licenses:
|
||||
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
|
||||
startActivity(lic);
|
||||
return true;
|
||||
case R.id.menu_help_release_notes:
|
||||
TextResourceDialog dialog = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
|
||||
getResources().getString(R.string.label_release_notes));
|
||||
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getSupportFragmentManager(), "release_notes");
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.ActionBarDrawerToggle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
@ -16,6 +15,8 @@ import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBar.Tab;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -51,7 +52,6 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
/**
|
||||
* Router variables
|
||||
*/
|
||||
protected String _myDir;
|
||||
protected boolean _isBound;
|
||||
protected boolean _triedBind;
|
||||
protected ServiceConnection _connection;
|
||||
@ -60,13 +60,16 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
|
||||
private static final String SHARED_PREFS = "net.i2p.android.router";
|
||||
protected static final String PREF_AUTO_START = "autoStart";
|
||||
/** true leads to a poor install experience, very slow to paint the screen */
|
||||
/**
|
||||
* true leads to a poor install experience, very slow to paint the screen
|
||||
*/
|
||||
protected static final boolean DEFAULT_AUTO_START = false;
|
||||
protected static final String PREF_NAV_DRAWER_OPENED = "navDrawerOpened";
|
||||
|
||||
/**
|
||||
* Override this in subclasses that need a ViewPager, such as a
|
||||
* category view.
|
||||
*
|
||||
* @return whether this Activity needs a ViewPager.
|
||||
*/
|
||||
protected boolean useViewPager() {
|
||||
@ -76,20 +79,21 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
/**
|
||||
* Override this in subclasses that can use two panes, such as a
|
||||
* list/detail class.
|
||||
*
|
||||
* @return whether this Activity can use a two-pane layout.
|
||||
*/
|
||||
protected boolean canUseTwoPanes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Util.d(this + " onCreate called");
|
||||
super.onCreate(savedInstanceState);
|
||||
_sharedPrefs = getSharedPreferences(SHARED_PREFS, 0);
|
||||
_myDir = getFilesDir().getAbsolutePath();
|
||||
|
||||
// If the Activity wants to use a ViewPager, provide it.
|
||||
// If the Activity can make use of two panes (if available),
|
||||
@ -102,6 +106,10 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
else
|
||||
setContentView(R.layout.activity_navdrawer_onepane);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
mTitle = mDrawerTitle = getTitle();
|
||||
String[] activityTitles = getResources().getStringArray(R.array.navdrawer_activity_titles);
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("i2pandroid.main.showStats", false)) {
|
||||
@ -118,17 +126,13 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
mDrawerList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
// Set the adapter for the list view
|
||||
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_list_item_1, activityTitles));
|
||||
mDrawerList.setAdapter(new ArrayAdapter<>(this,
|
||||
R.layout.listitem_navdrawer, activityTitles));
|
||||
// Set the list's click listener
|
||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||
|
||||
// Enable ActionBar app icon to behave as action to toggle nav drawer
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
|
||||
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
|
||||
R.string.drawer_open, R.string.drawer_close) {
|
||||
private boolean wasDragged = false;
|
||||
|
||||
/** Called when a drawer has settled in a completely closed state. */
|
||||
@ -167,57 +171,63 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
|
||||
private void selectItem(int pos) {
|
||||
switch (pos) {
|
||||
case 1:
|
||||
Intent news = new Intent(I2PActivityBase.this, NewsActivity.class);
|
||||
startActivity(news);
|
||||
break;
|
||||
case 2:
|
||||
Intent ab = new Intent(I2PActivityBase.this, AddressbookActivity.class);
|
||||
startActivity(ab);
|
||||
break;
|
||||
case 3:
|
||||
Intent itb = new Intent(I2PActivityBase.this, TunnelListActivity.class);
|
||||
startActivity(itb);
|
||||
break;
|
||||
case 4:
|
||||
Intent log = new Intent(I2PActivityBase.this, LogActivity.class);
|
||||
startActivity(log);
|
||||
break;
|
||||
case 5:
|
||||
Intent wp = new Intent(I2PActivityBase.this, WebActivity.class);
|
||||
wp.putExtra(WebFragment.HTML_RESOURCE_ID, R.raw.welcome_html);
|
||||
startActivity(wp);
|
||||
break;
|
||||
case 6:
|
||||
Intent active = new Intent(I2PActivityBase.this, RateGraphActivity.class);
|
||||
startActivity(active);
|
||||
break;
|
||||
case 7:
|
||||
Intent peers = new Intent(I2PActivityBase.this, PeersActivity.class);
|
||||
startActivity(peers);
|
||||
break;
|
||||
case 8:
|
||||
Intent netdb = new Intent(I2PActivityBase.this, NetDbActivity.class);
|
||||
startActivity(netdb);
|
||||
break;
|
||||
default:
|
||||
Intent main = new Intent(I2PActivityBase.this, MainActivity.class);
|
||||
startActivity(main);
|
||||
break;
|
||||
case 1:
|
||||
if (!(this instanceof NewsActivity)) {
|
||||
Intent news = new Intent(I2PActivityBase.this, NewsActivity.class);
|
||||
startActivity(news);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
Intent ab = new Intent(I2PActivityBase.this, AddressbookActivity.class);
|
||||
startActivity(ab);
|
||||
break;
|
||||
case 3:
|
||||
Intent itb = new Intent(I2PActivityBase.this, TunnelListActivity.class);
|
||||
startActivity(itb);
|
||||
break;
|
||||
case 4:
|
||||
if (!(this instanceof LogActivity)) {
|
||||
Intent log = new Intent(I2PActivityBase.this, LogActivity.class);
|
||||
startActivity(log);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
Intent wp = new Intent(I2PActivityBase.this, WebActivity.class);
|
||||
wp.putExtra(WebFragment.HTML_RESOURCE_ID, R.raw.welcome_html);
|
||||
startActivity(wp);
|
||||
break;
|
||||
case 6:
|
||||
if (!(this instanceof RateGraphActivity)) {
|
||||
Intent active = new Intent(I2PActivityBase.this, RateGraphActivity.class);
|
||||
startActivity(active);
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
Intent peers = new Intent(I2PActivityBase.this, PeersActivity.class);
|
||||
startActivity(peers);
|
||||
break;
|
||||
case 8:
|
||||
if (!(this instanceof NetDbActivity)) {
|
||||
Intent netdb = new Intent(I2PActivityBase.this, NetDbActivity.class);
|
||||
startActivity(netdb);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Intent main = new Intent(I2PActivityBase.this, MainActivity.class);
|
||||
startActivity(main);
|
||||
break;
|
||||
}
|
||||
mDrawerLayout.closeDrawer(mDrawerList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestart()
|
||||
{
|
||||
public void onRestart() {
|
||||
Util.d(this + " onRestart called");
|
||||
super.onRestart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart()
|
||||
{
|
||||
public void onStart() {
|
||||
Util.d(this + " onStart called");
|
||||
super.onStart();
|
||||
if (_sharedPrefs.getBoolean(PREF_AUTO_START, DEFAULT_AUTO_START))
|
||||
@ -226,24 +236,32 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
bindRouter(false);
|
||||
}
|
||||
|
||||
/** @param def default */
|
||||
/**
|
||||
* @param def default
|
||||
*/
|
||||
public boolean getPref(String pref, boolean def) {
|
||||
return _sharedPrefs.getBoolean(pref, def);
|
||||
}
|
||||
|
||||
/** @param def default */
|
||||
/**
|
||||
* @param def default
|
||||
*/
|
||||
public String getPref(String pref, String def) {
|
||||
return _sharedPrefs.getString(pref, def);
|
||||
}
|
||||
|
||||
/** @return success */
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
public boolean setPref(String pref, boolean val) {
|
||||
SharedPreferences.Editor edit = _sharedPrefs.edit();
|
||||
edit.putBoolean(pref, val);
|
||||
return edit.commit();
|
||||
}
|
||||
|
||||
/** @return success */
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
public boolean setPref(String pref, String val) {
|
||||
SharedPreferences.Editor edit = _sharedPrefs.edit();
|
||||
edit.putString(pref, val);
|
||||
@ -251,37 +269,32 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
public void onResume() {
|
||||
Util.d(this + " onResume called");
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
public void onPause() {
|
||||
Util.d(this + " onPause called");
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
Util.d(this + " onSaveInstanceState called");
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop()
|
||||
{
|
||||
public void onStop() {
|
||||
Util.d(this + " onStop called");
|
||||
unbindRouter();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
public void onDestroy() {
|
||||
Util.d(this + " onDestroy called");
|
||||
super.onDestroy();
|
||||
}
|
||||
@ -301,6 +314,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
/**
|
||||
* Override in subclass with e.g.
|
||||
* menu.findItem(R.id.action_add_to_addressbook).setVisible(!drawerOpen);
|
||||
*
|
||||
* @param drawerOpen true if the drawer is open
|
||||
*/
|
||||
protected void onDrawerChange(boolean drawerOpen) {
|
||||
@ -310,8 +324,11 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// The action bar home/up action should open or close the drawer.
|
||||
// ActionBarDrawerToggle will take care of this.
|
||||
if(mDrawerToggle.onOptionsItemSelected(item)) {
|
||||
if (mDrawerToggle.onOptionsItemSelected(item))
|
||||
return true;
|
||||
else if (item.getItemId() == android.R.id.home) {
|
||||
// This happens when mDrawerToggle.setDrawerIndicatorEnabled(false)
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
// Handle action buttons and overflow
|
||||
@ -361,7 +378,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
////// Service stuff
|
||||
|
||||
/**
|
||||
* Start the service and bind to it
|
||||
* Start the service and bind to it
|
||||
*/
|
||||
protected boolean startRouter() {
|
||||
Intent intent = new Intent();
|
||||
@ -378,7 +395,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind only
|
||||
* Bind only
|
||||
*/
|
||||
protected boolean bindRouter(boolean autoCreate) {
|
||||
Intent intent = new Intent(RouterBinder.class.getName());
|
||||
@ -393,7 +410,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
protected void unbindRouter() {
|
||||
Util.d(this + " unbindRouter called with _isBound:" + _isBound + " _connection:" + _connection + " _triedBind:" + _triedBind);
|
||||
if (_triedBind && _connection != null)
|
||||
unbindService(_connection);
|
||||
unbindService(_connection);
|
||||
|
||||
_triedBind = false;
|
||||
_connection = null;
|
||||
@ -424,7 +441,9 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
/** callback from ServiceConnection, override as necessary */
|
||||
/**
|
||||
* callback from ServiceConnection, override as necessary
|
||||
*/
|
||||
protected void onRouterBind(RouterService svc) {
|
||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (f instanceof I2PFragmentBase)
|
||||
@ -441,8 +460,11 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
/** callback from ServiceConnection, override as necessary */
|
||||
protected void onRouterUnbind() {}
|
||||
/**
|
||||
* callback from ServiceConnection, override as necessary
|
||||
*/
|
||||
protected void onRouterUnbind() {
|
||||
}
|
||||
|
||||
// I2PFragmentBase.RouterContextProvider
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
public interface I2PConstants {
|
||||
public static final String ANDROID_PREF_PREFIX = "i2pandroid.";
|
||||
}
|
@ -4,9 +4,12 @@ import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -15,12 +18,6 @@ import java.util.Properties;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
// Wouldn't this be better as a private class in MainActivity?
|
||||
|
||||
class InitActivities {
|
||||
@ -36,8 +33,7 @@ class InitActivities {
|
||||
|
||||
public InitActivities(Context c) {
|
||||
ctx = c;
|
||||
// This needs to be changed so that we can have an alternative place
|
||||
myDir = c.getFilesDir().getAbsolutePath();
|
||||
myDir = Util.getFileDir(c);
|
||||
_ourVersion = Util.getOurVersion(c);
|
||||
}
|
||||
|
||||
@ -128,8 +124,7 @@ class InitActivities {
|
||||
File certificates = new File(myDir, "certificates");
|
||||
File[] allcertificates = certificates.listFiles();
|
||||
if ( allcertificates != null) {
|
||||
for (int i = 0; i < allcertificates.length; i++) {
|
||||
File f = allcertificates[i];
|
||||
for (File f : allcertificates) {
|
||||
Util.d("Deleting old certificate file/dir " + f);
|
||||
FileUtil.rmdir(f, false);
|
||||
}
|
||||
@ -231,41 +226,13 @@ class InitActivities {
|
||||
/**
|
||||
* Load defaults from resource,
|
||||
* then add props from settings,
|
||||
* and write back
|
||||
* and write back.
|
||||
*
|
||||
* @param f relative to base dir
|
||||
* @param overrides local overrides or null
|
||||
*/
|
||||
public void mergeResourceToFile(int resID, String f, Properties overrides) {
|
||||
InputStream in = null;
|
||||
InputStream fin = null;
|
||||
|
||||
try {
|
||||
in = ctx.getResources().openRawResource(resID);
|
||||
Properties props = new OrderedProperties();
|
||||
try {
|
||||
fin = new FileInputStream(new File(myDir, f));
|
||||
DataHelper.loadProps(props, fin);
|
||||
Util.d("Merging resource into file " + f);
|
||||
} catch (IOException ioe) {
|
||||
Util.d("Creating file " + f + " from resource");
|
||||
}
|
||||
|
||||
// write in default settings
|
||||
DataHelper.loadProps(props, in);
|
||||
|
||||
// override with user settings
|
||||
if (overrides != null)
|
||||
props.putAll(overrides);
|
||||
File path = new File(myDir, f);
|
||||
DataHelper.storeProps(props, path);
|
||||
Util.d("Saved " + props.size() +" properties in " + f);
|
||||
} catch (IOException ioe) {
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
private void mergeResourceToFile(int resID, String f, Properties overrides) {
|
||||
Util.mergeResourceToFile(ctx, myDir, f, resID, overrides, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class LicenseActivity extends I2PActivityBase {
|
||||
|
@ -5,7 +5,7 @@ import android.support.v4.app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
|
||||
public class LicenseFragment extends ListFragment {
|
||||
@ -28,7 +28,7 @@ public class LicenseFragment extends ListFragment {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, names));
|
||||
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,36 +1,29 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import net.i2p.android.help.HelpActivity;
|
||||
import net.i2p.android.router.dialog.AboutDialog;
|
||||
import net.i2p.android.router.dialog.TextResourceDialog;
|
||||
import net.i2p.android.router.service.IRouterState;
|
||||
import net.i2p.android.router.service.IRouterStateCallback;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.service.State;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MainActivity extends I2PActivityBase implements
|
||||
MainFragment.RouterControlListener {
|
||||
@ -88,7 +81,7 @@ public class MainActivity extends I2PActivityBase implements
|
||||
|
||||
private void autoStart() {
|
||||
if (canStart()) {
|
||||
if (Util.isConnected(this)) {
|
||||
if (Connectivity.isConnected(this)) {
|
||||
mAutoStartFromIntent = true;
|
||||
onStartRouterClicked();
|
||||
} else {
|
||||
@ -140,18 +133,11 @@ public class MainActivity extends I2PActivityBase implements
|
||||
dialog.show(getSupportFragmentManager(), "about");
|
||||
return true;
|
||||
|
||||
// TODO: Unhide when Help page finished
|
||||
//case R.id.menu_help:
|
||||
// Intent hi = new Intent(MainActivity.this, HelpActivity.class);
|
||||
// hi.putExtra(HelpActivity.REFERRER, "main");
|
||||
// startActivity(hi);
|
||||
// return true;
|
||||
|
||||
// TODO: Remove when help page finished
|
||||
case R.id.menu_help_licenses:
|
||||
Intent lic = new Intent(MainActivity.this, LicenseActivity.class);
|
||||
startActivity(lic);
|
||||
case R.id.menu_help:
|
||||
Intent hi = new Intent(MainActivity.this, HelpActivity.class);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
|
||||
case R.id.menu_help_release_notes:
|
||||
TextResourceDialog rDdialog = new TextResourceDialog();
|
||||
Bundle args = new Bundle();
|
||||
@ -249,24 +235,35 @@ public class MainActivity extends I2PActivityBase implements
|
||||
private static final int STATE_MSG = 1;
|
||||
private static final String MSG_DATA = "state";
|
||||
|
||||
private Handler mHandler = new Handler() {
|
||||
private Handler mHandler = new StateHandler(new WeakReference<>(this));
|
||||
private static class StateHandler extends Handler {
|
||||
WeakReference<MainActivity> mReference;
|
||||
|
||||
public StateHandler(WeakReference<MainActivity> reference) {
|
||||
mReference = reference;
|
||||
}
|
||||
|
||||
private State lastRouterState = null;
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
MainActivity parent = mReference.get();
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
switch (msg.what) {
|
||||
case STATE_MSG:
|
||||
State state = msg.getData().getParcelable(MSG_DATA);
|
||||
if (lastRouterState == null || lastRouterState != state) {
|
||||
if (mMainFragment == null)
|
||||
mMainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (mMainFragment != null) {
|
||||
mMainFragment.updateState(state);
|
||||
if (parent.mMainFragment == null)
|
||||
parent.mMainFragment = (MainFragment) parent.getSupportFragmentManager().findFragmentById(R.id.main_fragment);
|
||||
if (parent.mMainFragment != null) {
|
||||
parent.mMainFragment.updateState(state);
|
||||
lastRouterState = state;
|
||||
}
|
||||
|
||||
if (state == State.RUNNING && mAutoStartFromIntent) {
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
if (state == State.RUNNING && parent.mAutoStartFromIntent) {
|
||||
parent.setResult(RESULT_OK);
|
||||
parent.finish();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -274,7 +271,7 @@ public class MainActivity extends I2PActivityBase implements
|
||||
super.handleMessage(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean canStart() {
|
||||
RouterService svc = _routerService;
|
||||
@ -289,7 +286,7 @@ public class MainActivity extends I2PActivityBase implements
|
||||
// MainFragment.RouterControlListener
|
||||
|
||||
public boolean shouldShowOnOff() {
|
||||
return (canStart() && Util.isConnected(this)) || canStop();
|
||||
return (canStart() && Connectivity.isConnected(this)) || canStop();
|
||||
}
|
||||
|
||||
public boolean shouldBeOn() {
|
||||
@ -304,7 +301,7 @@ public class MainActivity extends I2PActivityBase implements
|
||||
setPref(PREF_AUTO_START, true);
|
||||
svc.manualStart();
|
||||
} else {
|
||||
(new File(_myDir, "wrapper.log")).delete();
|
||||
(new File(Util.getFileDir(this), "wrapper.log")).delete();
|
||||
startRouter();
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ package net.i2p.android.router;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -16,9 +18,11 @@ import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
|
||||
import net.i2p.android.router.dialog.FirstStartDialog;
|
||||
import net.i2p.android.router.dialog.VersionDialog;
|
||||
import net.i2p.android.router.service.State;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.LongToggleButton;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -44,6 +48,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
private String _savedStatus;
|
||||
private boolean _keep = true;
|
||||
private boolean _startPressed = false;
|
||||
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
|
||||
private static final String PREF_FIRST_START = "app.router.firstStart";
|
||||
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
|
||||
protected static final String PROP_NEW_INSTALL = "i2p.newInstall";
|
||||
@ -205,7 +210,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
public boolean onBackPressed() {
|
||||
RouterContext ctx = getRouterContext();
|
||||
// RouterService svc = _routerService; Which is better to use?!
|
||||
_keep = Util.isConnected(getActivity()) && (ctx != null || _startPressed);
|
||||
_keep = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
|
||||
Util.d("*********************************************************");
|
||||
Util.d("Back pressed, Keep? " + _keep);
|
||||
Util.d("*********************************************************");
|
||||
@ -264,7 +269,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
|
||||
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
|
||||
|
||||
if(!Util.isConnected(getActivity())) {
|
||||
if(!Connectivity.isConnected(getActivity())) {
|
||||
// Manually set state, RouterService won't be running
|
||||
updateState(State.WAITING);
|
||||
vStatusText.setText("No Internet connection is available");
|
||||
@ -397,24 +402,24 @@ public class MainFragment extends I2PFragmentBase {
|
||||
String name = getName(ctx, client);
|
||||
Hash h = client.calculateHash();
|
||||
TableRow dest = new TableRow(getActivity());
|
||||
//dest.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
|
||||
dest.setPadding(16, 4, 0, 4);
|
||||
|
||||
// Client or server
|
||||
ImageView type = new ImageView(getActivity());
|
||||
type.setPadding(6, 6, 6, 6);
|
||||
TextView type = new TextView(getActivity());
|
||||
type.setTextColor(getResources().getColor(android.R.color.primary_text_light));
|
||||
type.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
type.setGravity(Gravity.CENTER);
|
||||
if (ctx.clientManager().shouldPublishLeaseSet(h))
|
||||
type.setImageDrawable(getActivity().getResources()
|
||||
.getDrawable(R.drawable.server));
|
||||
type.setText(R.string.char_server_tunnel);
|
||||
else
|
||||
type.setImageDrawable(getActivity().getResources()
|
||||
.getDrawable(R.drawable.client));
|
||||
type.setText(R.string.char_client_tunnel);
|
||||
dest.addView(type);
|
||||
|
||||
// Name
|
||||
TextView destName = new TextView(getActivity());
|
||||
destName.setPadding(6, 0, 0, 0);
|
||||
destName.setPadding(16, 0, 0, 0);
|
||||
destName.setGravity(Gravity.CENTER_VERTICAL);
|
||||
destName.setText(name);
|
||||
//destName.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
|
||||
dest.addView(destName);
|
||||
|
||||
// Status
|
||||
@ -423,14 +428,14 @@ public class MainFragment extends I2PFragmentBase {
|
||||
long timeToExpire = ls.getEarliestLeaseDate() - ctx.clock().now();
|
||||
if (timeToExpire < 0) {
|
||||
// red or yellow light
|
||||
type.setBackgroundColor(Color.TRANSPARENT);
|
||||
type.setBackgroundResource(R.drawable.tunnel_yellow);
|
||||
} else {
|
||||
// green light
|
||||
type.setBackgroundColor(Color.GREEN);
|
||||
type.setBackgroundResource(R.drawable.tunnel_green);
|
||||
}
|
||||
} else {
|
||||
// yellow light
|
||||
type.setBackgroundColor(Color.TRANSPARENT);
|
||||
type.setBackgroundResource(R.drawable.tunnel_yellow);
|
||||
}
|
||||
|
||||
dests.addView(dest);
|
||||
@ -438,7 +443,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
} else {
|
||||
TableRow empty = new TableRow(getActivity());
|
||||
TextView emptyText = new TextView(getActivity());
|
||||
emptyText.setText("No client tunnels are running yet.");
|
||||
emptyText.setText(R.string.no_client_tunnels_running);
|
||||
empty.addView(emptyText);
|
||||
dests.addView(empty);
|
||||
}
|
||||
@ -487,14 +492,23 @@ public class MainFragment extends I2PFragmentBase {
|
||||
}
|
||||
|
||||
private void checkDialog() {
|
||||
VersionDialog dialog = new VersionDialog();
|
||||
I2PActivityBase ab = (I2PActivityBase) getActivity();
|
||||
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
|
||||
if (configureBrowser) {
|
||||
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
|
||||
ab.setPref(PREF_CONFIGURE_BROWSER, false);
|
||||
}
|
||||
/*VersionDialog dialog = new VersionDialog();
|
||||
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
|
||||
if(oldVersion.equals("??")) {
|
||||
// TODO Don't show this dialog until it is reworked
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_INSTALL);
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "newinstall");
|
||||
} else {
|
||||
// TODO Don't show dialog on new version until we have something new to tell them
|
||||
String currentVersion = Util.getOurVersion(getActivity());
|
||||
if(!oldVersion.equals(currentVersion)) {
|
||||
Bundle args = new Bundle();
|
||||
@ -502,7 +516,7 @@ public class MainFragment extends I2PFragmentBase {
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getActivity().getSupportFragmentManager(), "newversion");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void checkFirstStart() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.i2p.android.router;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class NewsActivity extends I2PActivityBase {
|
||||
|
@ -2,77 +2,57 @@ package net.i2p.android.router;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.apps.NewsFetcher;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import net.i2p.android.apps.NewsFetcher;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.web.I2PWebViewClient;
|
||||
|
||||
public class NewsFragment extends I2PFragmentBase {
|
||||
|
||||
private I2PWebViewClient _wvClient;
|
||||
private long _lastChanged;
|
||||
|
||||
private static final String WARNING = "Warning - while the news is fetched over I2P, " +
|
||||
"any non-I2P links visited in this window are fetched over the regular internet and are " +
|
||||
"not anonymous.\n";
|
||||
|
||||
|
||||
// TODO add some inline style
|
||||
private static final String HEADER = "<html><head></head><body>";
|
||||
private static final String FOOTER = "</body></html>";
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState)
|
||||
{
|
||||
View v = inflater.inflate(R.layout.fragment_news, container, false);
|
||||
WebView wv = (WebView) v.findViewById(R.id.news_webview);
|
||||
wv.getSettings().setLoadsImagesAutomatically(false);
|
||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||
wv.getSettings().setUseWideViewPort(true);
|
||||
_wvClient = new I2PWebViewClient();
|
||||
wv.setWebViewClient(_wvClient);
|
||||
wv.getSettings().setBuiltInZoomControls(true);
|
||||
return v;
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_news, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
NewsFetcher nf = NewsFetcher.getInstance();
|
||||
if (nf != null) {
|
||||
// always update the text
|
||||
// Always update the status
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
|
||||
tv.setText(WARNING + nf.status().replace(" ", " "));
|
||||
tv.setText(nf.status().replace(" ", " "));
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// only update the webview if we need to
|
||||
// XXX Gets dir directly instead of the one stored in the Activity (for now)
|
||||
File newsFile = new File(getActivity().getFilesDir().getAbsolutePath(), "docs/news.xml");
|
||||
// Only update the content if we need to
|
||||
File newsFile = new File(Util.getFileDir(getActivity()), "docs/news.xml");
|
||||
boolean newsExists = newsFile.exists();
|
||||
if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
|
||||
return;
|
||||
_lastChanged = System.currentTimeMillis();
|
||||
|
||||
WebView wv = (WebView) getActivity().findViewById(R.id.news_webview);
|
||||
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
|
||||
byte buf[] = new byte[1024];
|
||||
try {
|
||||
if (newsExists) {
|
||||
out.write(HEADER.getBytes());
|
||||
in = new FileInputStream(newsFile);
|
||||
} else {
|
||||
in = getResources().openRawResource(R.raw.initialnews_html);
|
||||
@ -82,30 +62,34 @@ public class NewsFragment extends I2PFragmentBase {
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
|
||||
if (newsExists)
|
||||
out.write(FOOTER.getBytes());
|
||||
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("news error " + ioe);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
String news = "";
|
||||
try {
|
||||
String news = out.toString("UTF-8");
|
||||
wv.loadData(news, "text/html", "UTF-8");
|
||||
news = out.toString("UTF-8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
WebView wv = (WebView) getActivity().findViewById(R.id.news_webview);
|
||||
_wvClient.cancelAll();
|
||||
wv.stopLoading();
|
||||
if (wv.canGoBack()) {
|
||||
wv.goBack();
|
||||
return true;
|
||||
// Get SpannableStringBuilder object from HTML code
|
||||
CharSequence sequence = Html.fromHtml(news);
|
||||
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
|
||||
|
||||
// Get an array of URLSpan from SpannableStringBuilder object
|
||||
URLSpan[] urlSpans = strBuilder.getSpans(0, strBuilder.length(), URLSpan.class);
|
||||
|
||||
// Remove URLSpans with relative paths, which can't be clicked on
|
||||
for (final URLSpan span : urlSpans) {
|
||||
if (span.getURL().startsWith("/"))
|
||||
strBuilder.removeSpan(span);
|
||||
}
|
||||
return false;
|
||||
|
||||
TextView tv = (TextView) getActivity().findViewById(R.id.news_content);
|
||||
tv.setText(strBuilder);
|
||||
tv.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
}
|
||||
|
@ -6,24 +6,21 @@ import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.service.StatSummarizer;
|
||||
import net.i2p.android.router.util.PortPreference;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.stat.FrequencyStat;
|
||||
@ -31,7 +28,12 @@ import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.LogManager;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
public class SettingsActivity extends PreferenceActivity {
|
||||
// Actions for legacy settings
|
||||
@ -40,6 +42,8 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
private static final String ACTION_PREFS_LOGGING = "net.i2p.android.router.PREFS_LOGGING";
|
||||
private static final String ACTION_PREFS_ADVANCED = "net.i2p.android.router.PREFS_ADVANCED";
|
||||
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -47,31 +51,59 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
String action = getIntent().getAction();
|
||||
if (action != null) {
|
||||
if (ACTION_PREFS_NET.equals(action)) {
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
} else if (ACTION_PREFS_GRAPHS.equals(action)){
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
setupGraphSettings(this, getPreferenceScreen(), getRouterContext());
|
||||
} else if (ACTION_PREFS_LOGGING.equals(action)) {
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx != null)
|
||||
setupLoggingSettings(this, getPreferenceScreen(), ctx);
|
||||
} else if (ACTION_PREFS_ADVANCED.equals(action)) {
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
switch (action) {
|
||||
case ACTION_PREFS_NET:
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
break;
|
||||
case ACTION_PREFS_GRAPHS:
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
setupGraphSettings(this, getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case ACTION_PREFS_LOGGING:
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case ACTION_PREFS_ADVANCED:
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
setupAdvancedSettings(this, getPreferenceScreen());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Load any properties that the router might have changed on us.
|
||||
setupPreferences(this, Util.getRouterContext());
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Load the legacy preferences headers
|
||||
addPreferencesFromResource(R.xml.settings_headers_legacy);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
// Load the legacy preferences headers
|
||||
addPreferencesFromResource(R.xml.settings_headers_legacy);
|
||||
}
|
||||
|
||||
mToolbar.setTitle(getTitle());
|
||||
}
|
||||
|
||||
protected static RouterContext getRouterContext() {
|
||||
List<RouterContext> contexts = RouterContext.listContexts();
|
||||
if ( !((contexts == null) || (contexts.isEmpty())) ) {
|
||||
return contexts.get(0);
|
||||
protected static void setupPreferences(Context context, RouterContext ctx) {
|
||||
if (ctx != null) {
|
||||
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
|
||||
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
|
||||
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
|
||||
int udpPort = ctx.getProperty(udpPortKey, -1);
|
||||
int ntcpPort = ctx.getProperty(ntcpPortKey, -1);
|
||||
boolean ntcpAutoPort = ctx.getBooleanPropertyDefaultTrue(ntcpAutoPortKey);
|
||||
if (ntcpPort < 0 && ntcpAutoPort)
|
||||
ntcpPort = udpPort;
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (prefs.getInt(udpPortKey, -1) != udpPort ||
|
||||
prefs.getInt(ntcpPortKey, -1) != ntcpPort) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(udpPortKey, udpPort);
|
||||
editor.putInt(ntcpPortKey, ntcpPort);
|
||||
// commit() instead of apply() because this needs to happen
|
||||
// before SettingsActivity loads its Preferences.
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
|
||||
@ -153,13 +185,74 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
buf.append(prefix).append('=').append(level).append('\n');
|
||||
}
|
||||
*/
|
||||
/* Don't show, there are no settings that require the router
|
||||
} else {
|
||||
PreferenceCategory noRouter = new PreferenceCategory(context);
|
||||
noRouter.setTitle(R.string.router_not_running);
|
||||
ps.addPreference(noRouter);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setupAdvancedSettings(final Context context, PreferenceScreen ps) {
|
||||
final String udpEnableKey = context.getString(R.string.PROP_ENABLE_UDP);
|
||||
final String ntcpEnableKey = context.getString(R.string.PROP_ENABLE_NTCP);
|
||||
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
|
||||
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
|
||||
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
|
||||
final CheckBoxPreference udpEnable = (CheckBoxPreference) ps.findPreference(udpEnableKey);
|
||||
final CheckBoxPreference ntcpEnable = (CheckBoxPreference) ps.findPreference(ntcpEnableKey);
|
||||
final PortPreference udpPort = (PortPreference) ps.findPreference(udpPortKey);
|
||||
final PortPreference ntcpPort = (PortPreference) ps.findPreference(ntcpPortKey);
|
||||
final CheckBoxPreference ntcpAutoPort = (CheckBoxPreference) ps.findPreference(ntcpAutoPortKey);
|
||||
|
||||
udpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Boolean checked = (Boolean) newValue;
|
||||
if (checked || ntcpEnable.isChecked())
|
||||
return true;
|
||||
else {
|
||||
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ntcpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Boolean checked = (Boolean) newValue;
|
||||
if (checked || udpEnable.isChecked())
|
||||
return true;
|
||||
else {
|
||||
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
udpPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (ntcpAutoPort.isChecked())
|
||||
ntcpPort.setText((String) newValue);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
ntcpAutoPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final Boolean checked = (Boolean) newValue;
|
||||
if (checked)
|
||||
ntcpPort.setText(udpPort.getText());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
public void onBuildHeaders(List<Header> target) {
|
||||
@ -170,24 +263,48 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
loadHeadersFromResource(R.xml.settings_headers, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(int layoutResID) {
|
||||
ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate(
|
||||
R.layout.activity_settings,
|
||||
(ViewGroup) getWindow().getDecorView().getRootView(), false);
|
||||
|
||||
mToolbar = (Toolbar) contentView.findViewById(R.id.main_toolbar);
|
||||
mToolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onBackPressed();
|
||||
}
|
||||
});
|
||||
|
||||
ViewGroup contentWrapper = (ViewGroup) contentView.findViewById(R.id.content_wrapper);
|
||||
LayoutInflater.from(this).inflate(layoutResID, contentWrapper, true);
|
||||
|
||||
getWindow().setContentView(contentView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
List<Properties> lProps = Util.getPropertiesFromPreferences(this);
|
||||
Properties props = lProps.get(0);
|
||||
Properties logSettings = lProps.get(1);
|
||||
Properties propsToRemove = lProps.get(1);
|
||||
Properties logSettings = lProps.get(2);
|
||||
|
||||
Set toRemove = propsToRemove.keySet();
|
||||
|
||||
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props, toRemove);
|
||||
|
||||
// Apply new config if we are running.
|
||||
List<RouterContext> contexts = RouterContext.listContexts();
|
||||
if ( !((contexts == null) || (contexts.isEmpty())) ) {
|
||||
RouterContext _context = contexts.get(0);
|
||||
_context.router().saveConfig(props, null);
|
||||
RouterContext rCtx = Util.getRouterContext();
|
||||
if (rCtx != null) {
|
||||
rCtx.router().saveConfig(props, toRemove);
|
||||
|
||||
// Merge in new log settings
|
||||
saveLoggingChanges(_context, logSettings);
|
||||
saveLoggingChanges(rCtx, logSettings);
|
||||
} else {
|
||||
// Merge in new config settings, write the file.
|
||||
InitActivities init = new InitActivities(this);
|
||||
init.mergeResourceToFile(R.raw.router_config, "router.config", props);
|
||||
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props, toRemove);
|
||||
|
||||
// Merge in new log settings
|
||||
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
|
||||
@ -195,6 +312,9 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
// Store the settings in Android
|
||||
super.onPause();
|
||||
|
||||
if (restartRequired)
|
||||
Toast.makeText(this, R.string.settings_router_restart_required, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void saveLoggingChanges(I2PAppContext ctx, Properties logSettings) {
|
||||
@ -223,20 +343,22 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String settings = getArguments().getString("settings");
|
||||
if ("net".equals(settings)) {
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
} else if ("graphs".equals(settings)) {
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx != null)
|
||||
setupGraphSettings(getActivity(), getPreferenceScreen(), ctx);
|
||||
} else if ("logging".equals(settings)) {
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
RouterContext ctx = getRouterContext();
|
||||
if (ctx != null)
|
||||
setupLoggingSettings(getActivity(), getPreferenceScreen(), ctx);
|
||||
} else if ("advanced".equals(settings)) {
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
switch (settings) {
|
||||
case "net":
|
||||
addPreferencesFromResource(R.xml.settings_net);
|
||||
break;
|
||||
case "graphs":
|
||||
addPreferencesFromResource(R.xml.settings_graphs);
|
||||
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case "logging":
|
||||
addPreferencesFromResource(R.xml.settings_logging);
|
||||
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
|
||||
break;
|
||||
case "advanced":
|
||||
addPreferencesFromResource(R.xml.settings_advanced);
|
||||
setupAdvancedSettings(getActivity(), getPreferenceScreen());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public class AddressEntryAdapter extends ArrayAdapter<AddressEntry> {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public AddressEntryAdapter(Context context) {
|
||||
super(context, R.layout.addressbook_list_item);
|
||||
super(context, R.layout.listitem_text);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ public class AddressEntryAdapter extends ArrayAdapter<AddressEntry> {
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = mInflater.inflate(R.layout.addressbook_list_item, parent, false);
|
||||
View v = mInflater.inflate(R.layout.listitem_text, parent, false);
|
||||
AddressEntry address = getItem(position);
|
||||
|
||||
TextView text = (TextView) v.findViewById(R.id.text);
|
||||
|
@ -1,42 +1,47 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.util.NamingServiceUtil;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.android.router.util.NamingServiceUtil;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
|
||||
private RouterContext mRContext;
|
||||
private I2PFragmentBase.RouterContextProvider mRContextProvider;
|
||||
private String mBook;
|
||||
private String mFilter;
|
||||
private List<AddressEntry> mData;
|
||||
|
||||
public AddressEntryLoader(Context context, RouterContext rContext,
|
||||
public AddressEntryLoader(Context context, I2PFragmentBase.RouterContextProvider rContextProvider,
|
||||
String book, String filter) {
|
||||
super(context);
|
||||
mRContext = rContext;
|
||||
mRContextProvider = rContextProvider;
|
||||
mBook = book;
|
||||
mFilter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AddressEntry> loadInBackground() {
|
||||
RouterContext routerContext = mRContextProvider.getRouterContext();
|
||||
if (routerContext == null)
|
||||
return null;
|
||||
|
||||
// get the names
|
||||
NamingService ns = NamingServiceUtil.getNamingService(mRContext, mBook);
|
||||
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
|
||||
Util.d("NamingService: " + ns.getName());
|
||||
// After router shutdown we get nothing... why?
|
||||
List<AddressEntry> ret = new ArrayList<AddressEntry>();
|
||||
Map<String, Destination> names = new TreeMap<String, Destination>();
|
||||
List<AddressEntry> ret = new ArrayList<>();
|
||||
Map<String, Destination> names = new TreeMap<>();
|
||||
|
||||
Properties searchProps = new Properties();
|
||||
// Needed for HostsTxtNamingService
|
||||
|
@ -1,9 +1,5 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.web.WebActivity;
|
||||
import net.i2p.android.router.web.WebFragment;
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
@ -12,11 +8,16 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBar.Tab;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class AddressbookActivity extends I2PActivityBase
|
||||
implements AddressbookFragment.OnAddressSelectedListener,
|
||||
@ -27,47 +28,36 @@ public class AddressbookActivity extends I2PActivityBase
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_TAB = "selected_tab";
|
||||
private static final String SELECTED_PAGE = "selected_page";
|
||||
private static final int PAGE_ROUTER = 0;
|
||||
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set up action bar for tabs
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
// Router book tab
|
||||
AddressbookFragment rf = new AddressbookFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(AddressbookFragment.BOOK_NAME,
|
||||
AddressbookFragment.ROUTER_BOOK);
|
||||
rf.setArguments(args);
|
||||
Tab tab = actionBar.newTab()
|
||||
.setText("Router")
|
||||
.setTabListener(new TabListener(rf));
|
||||
actionBar.addTab(tab);
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.addressbook_pages, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
// Private book tab
|
||||
AddressbookFragment pf = new AddressbookFragment();
|
||||
args = new Bundle();
|
||||
args.putString(AddressbookFragment.BOOK_NAME,
|
||||
AddressbookFragment.PRIVATE_BOOK);
|
||||
pf.setArguments(args);
|
||||
tab = actionBar.newTab()
|
||||
.setText("Private")
|
||||
.setTabListener(new TabListener(pf));
|
||||
actionBar.addTab(tab);
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectPage(i);
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_TAB);
|
||||
actionBar.setSelectedNavigationItem(selected);
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
@ -76,13 +66,30 @@ public class AddressbookActivity extends I2PActivityBase
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_PAGE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectPage(PAGE_ROUTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_TAB,
|
||||
getSupportActionBar().getSelectedNavigationIndex());
|
||||
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private void selectPage(int page) {
|
||||
AddressbookFragment f = new AddressbookFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(AddressbookFragment.BOOK_NAME,
|
||||
page == PAGE_ROUTER ?
|
||||
AddressbookFragment.ROUTER_BOOK :
|
||||
AddressbookFragment.PRIVATE_BOOK);
|
||||
f.setArguments(args);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -105,12 +112,8 @@ public class AddressbookActivity extends I2PActivityBase
|
||||
setResult(Activity.RESULT_OK, result);
|
||||
finish();
|
||||
} else {
|
||||
//Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
//i.setData(Uri.parse("http://" + host));
|
||||
// XXX: Temporarily reverting to inbuilt browser
|
||||
// until an alternative browser is ready.
|
||||
Intent i = new Intent(this, WebActivity.class);
|
||||
i.putExtra(WebFragment.HTML_URI, "http://" + host + '/');
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("http://" + host));
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import net.i2p.android.wizard.model.AbstractWizardModel;
|
||||
import net.i2p.android.wizard.ui.AbstractWizardActivity;
|
||||
@ -19,6 +20,7 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
|
||||
@Override
|
||||
protected DialogFragment onGetFinishWizardDialog() {
|
||||
return new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
|
@ -7,25 +7,29 @@ import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.addressbook.Daemon;
|
||||
import net.i2p.android.router.HelpActivity;
|
||||
import net.i2p.android.help.HelpActivity;
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.NamingServiceUtil;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AddressbookFragment extends ListFragment implements
|
||||
I2PFragmentBase.RouterContextUser,
|
||||
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
|
||||
@ -46,6 +50,8 @@ public class AddressbookFragment extends ListFragment implements
|
||||
private String mBook;
|
||||
private String mCurFilter;
|
||||
|
||||
private ImageButton mAddToAddressbook;
|
||||
|
||||
// Set in onActivityResult()
|
||||
private Intent mAddWizardData;
|
||||
|
||||
@ -84,6 +90,27 @@ public class AddressbookFragment extends ListFragment implements
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Create the list fragment's content view by calling the super method
|
||||
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
|
||||
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
|
||||
listContainer.addView(listFragmentView);
|
||||
|
||||
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
|
||||
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
|
||||
startActivityForResult(wi, ADD_WIZARD_REQUEST);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
@ -111,8 +138,8 @@ public class AddressbookFragment extends ListFragment implements
|
||||
// Show actions
|
||||
if (mSearchAddressbook != null)
|
||||
mSearchAddressbook.setVisible(true);
|
||||
if (mAddToAddressbook != null)
|
||||
mAddToAddressbook.setVisible(false);
|
||||
if (mAddToAddressbook != null && mAddToAddressbook.getVisibility() != View.VISIBLE)
|
||||
mAddToAddressbook.setVisibility(View.VISIBLE);
|
||||
|
||||
if (mAddWizardData != null) {
|
||||
// Save the new entry
|
||||
@ -140,24 +167,26 @@ public class AddressbookFragment extends ListFragment implements
|
||||
}
|
||||
|
||||
private MenuItem mSearchAddressbook;
|
||||
private MenuItem mAddToAddressbook;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
|
||||
|
||||
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
|
||||
mAddToAddressbook = menu.findItem(R.id.action_add_to_addressbook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
// Hide until needed
|
||||
if (getRouterContext() == null) {
|
||||
mSearchAddressbook.setVisible(false);
|
||||
mAddToAddressbook.setVisible(false);
|
||||
if (mAddToAddressbook != null)
|
||||
mAddToAddressbook.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Only allow adding to private book
|
||||
if (!PRIVATE_BOOK.equals(mBook)) {
|
||||
mAddToAddressbook.setVisible(false);
|
||||
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
|
||||
mAddToAddressbook.setVisibility(View.GONE);
|
||||
mAddToAddressbook = null;
|
||||
}
|
||||
}
|
||||
@ -167,27 +196,22 @@ public class AddressbookFragment extends ListFragment implements
|
||||
// Handle presses on the action bar items
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_add_to_addressbook:
|
||||
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
|
||||
startActivityForResult(wi, ADD_WIZARD_REQUEST);
|
||||
return true;
|
||||
case R.id.action_reload_subscriptions:
|
||||
Daemon.wakeup();
|
||||
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
case R.id.action_addressbook_settings:
|
||||
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
|
||||
startActivity(si);
|
||||
return true;
|
||||
// TODO: Enable when Help page finished
|
||||
//case R.id.action_addressbook_help:
|
||||
// Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||
// hi.putExtra(HelpActivity.REFERRER, "addressbook");
|
||||
// startActivity(hi);
|
||||
// return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
case R.id.action_reload_subscriptions:
|
||||
Daemon.wakeup();
|
||||
Toast.makeText(getActivity(), "Reloading subscriptions...",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
case R.id.action_addressbook_settings:
|
||||
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
|
||||
startActivity(si);
|
||||
return true;
|
||||
case R.id.action_addressbook_help:
|
||||
Intent hi = new Intent(getActivity(), HelpActivity.class);
|
||||
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
|
||||
startActivity(hi);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,27 +249,32 @@ public class AddressbookFragment extends ListFragment implements
|
||||
|
||||
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
|
||||
return new AddressEntryLoader(getActivity(),
|
||||
getRouterContext(), mBook, mCurFilter);
|
||||
mRouterContextProvider, mBook, mCurFilter);
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<List<AddressEntry>> loader,
|
||||
List<AddressEntry> data) {
|
||||
List<AddressEntry> data) {
|
||||
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
|
||||
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
|
||||
mAdapter.setData(data);
|
||||
if (data == null)
|
||||
setEmptyText(getResources().getString(
|
||||
R.string.router_not_running));
|
||||
else {
|
||||
mAdapter.setData(data);
|
||||
|
||||
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
|
||||
if (mCurFilter != null)
|
||||
v.setText(getActivity().getResources().getString(
|
||||
R.string.addressbook_search_header,
|
||||
data.size()));
|
||||
else
|
||||
v.setText("");
|
||||
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
|
||||
if (mCurFilter != null)
|
||||
v.setText(getActivity().getResources().getString(
|
||||
R.string.addressbook_search_header,
|
||||
data.size()));
|
||||
else
|
||||
v.setText("");
|
||||
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,22 @@
|
||||
package net.i2p.android.router.addressbook;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
public class AddressbookSettingsActivity extends Activity {
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AddressbookSettingsActivity extends ActionBarActivity {
|
||||
|
||||
private EditText text_content_subscriptions;
|
||||
private Button btn_save_subscriptions;
|
||||
@ -25,6 +27,12 @@ public class AddressbookSettingsActivity extends Activity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_addressbook_settings);
|
||||
|
||||
// Set the action bar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
text_content_subscriptions = (EditText) findViewById(R.id.subscriptions_content);
|
||||
btn_save_subscriptions = (Button) findViewById(R.id.button_save_subscriptions);
|
||||
init_actions();
|
||||
@ -32,12 +40,6 @@ public class AddressbookSettingsActivity extends Activity {
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_addressbook_settings, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void init_actions() {
|
||||
btn_save_subscriptions.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
|
@ -1,18 +1,24 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.LicenseActivity;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class AboutDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||
@ -33,7 +39,14 @@ public class AboutDialog extends DialogFragment {
|
||||
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.menu_about)
|
||||
.setView(view);
|
||||
.setView(view)
|
||||
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Intent lic = new Intent(getActivity(), LicenseActivity.class);
|
||||
startActivity(lic);
|
||||
}
|
||||
});
|
||||
return b.create();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import net.i2p.android.help.BrowserConfigActivity;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
public class ConfigureBrowserDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.configure_browser_title)
|
||||
.setMessage(R.string.configure_browser_for_i2p)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.dismiss();
|
||||
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
|
||||
startActivity(hi);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.cancel();
|
||||
}
|
||||
});
|
||||
return b.create();
|
||||
}
|
||||
}
|
@ -1,19 +1,22 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.I2Patterns;
|
||||
|
||||
public class FirstStartDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
LayoutInflater li = LayoutInflater.from(getActivity());
|
||||
View view = li.inflate(R.layout.fragment_dialog_first_start, null);
|
||||
|
||||
@ -24,7 +27,7 @@ public class FirstStartDialog extends DialogFragment {
|
||||
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
b.setTitle(R.string.first_start_title)
|
||||
.setView(view);
|
||||
.setView(view);
|
||||
return b.create();
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,18 @@ package net.i2p.android.router.dialog;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
/**
|
||||
* Display a raw text resource.
|
||||
@ -30,7 +31,6 @@ public class TextResourceDialog extends DialogFragment {
|
||||
{
|
||||
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
|
||||
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
|
||||
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
|
||||
String title = getArguments().getString(TEXT_DIALOG_TITLE);
|
||||
if (title != null)
|
||||
getDialog().setTitle(title);
|
||||
@ -54,10 +54,8 @@ public class TextResourceDialog extends DialogFragment {
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("resource error " + ioe);
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
System.err.println("resource error " + nfe);
|
||||
} catch (IOException | Resources.NotFoundException re) {
|
||||
System.err.println("resource error " + re);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
@ -1,24 +1,27 @@
|
||||
package net.i2p.android.router.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.MainFragment;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
public class VersionDialog extends DialogFragment {
|
||||
public static final String DIALOG_TYPE = "dialog_type";
|
||||
public static final int DIALOG_NEW_INSTALL = 0;
|
||||
public static final int DIALOG_NEW_VERSION = 1;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle SavedInstanceState) {
|
||||
final String currentVersion = Util.getOurVersion(getActivity());
|
||||
Dialog rv = null;
|
||||
Dialog rv;
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
|
||||
int id = getArguments().getInt(DIALOG_TYPE);
|
||||
switch(id) {
|
||||
@ -53,6 +56,7 @@ public class VersionDialog extends DialogFragment {
|
||||
break;
|
||||
|
||||
case DIALOG_NEW_VERSION:
|
||||
default:
|
||||
b.setMessage(getResources().getString(R.string.welcome_new_version) +
|
||||
" " + currentVersion)
|
||||
.setCancelable(true)
|
||||
|
@ -1,17 +1,19 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.SettingsActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
|
||||
public class LogActivity extends I2PActivityBase implements
|
||||
LogFragment.OnEntrySelectedListener {
|
||||
@ -23,6 +25,9 @@ public class LogActivity extends I2PActivityBase implements
|
||||
|
||||
private static final String SELECTED_LEVEL = "selected_level";
|
||||
|
||||
private String[] mLevels;
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
return true;
|
||||
@ -32,9 +37,10 @@ public class LogActivity extends I2PActivityBase implements
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set up action bar for drop-down list
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||
mLevels = getResources().getStringArray(R.array.log_level_list);
|
||||
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(false);
|
||||
|
||||
@ -46,31 +52,36 @@ public class LogActivity extends I2PActivityBase implements
|
||||
mTwoPane = true;
|
||||
}
|
||||
|
||||
SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
|
||||
R.array.log_level_list, android.R.layout.simple_spinner_dropdown_item);
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.log_level_list, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
ActionBar.OnNavigationListener mNavigationListener = new ActionBar.OnNavigationListener() {
|
||||
String[] levels = getResources().getStringArray(R.array.log_level_list);
|
||||
|
||||
public boolean onNavigationItemSelected(int position, long itemId) {
|
||||
String level = levels[position];
|
||||
LogFragment f = LogFragment.newInstance(level);
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
f.setActivateOnItemClick(true);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f, levels[position]).commit();
|
||||
return true;
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectLevel(i);
|
||||
}
|
||||
};
|
||||
|
||||
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationListener);
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_LEVEL);
|
||||
actionBar.setSelectedNavigationItem(selected);
|
||||
}
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectLevel(0);
|
||||
}
|
||||
|
||||
private void selectLevel(int i) {
|
||||
String level = mLevels[i];
|
||||
LogFragment f = LogFragment.newInstance(level);
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
f.setActivateOnItemClick(true);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f, level).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -106,8 +117,7 @@ public class LogActivity extends I2PActivityBase implements
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_LEVEL,
|
||||
getSupportActionBar().getSelectedNavigationIndex());
|
||||
outState.putInt(SELECTED_LEVEL, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
// LogFragment.OnEntrySelectedListener
|
||||
|
@ -2,12 +2,19 @@ package net.i2p.android.router.log;
|
||||
|
||||
import net.i2p.android.router.I2PFragmentBase;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class LogDetailFragment extends I2PFragmentBase {
|
||||
public static final String LOG_ENTRY = "log_entry";
|
||||
@ -22,6 +29,12 @@ public class LogDetailFragment extends I2PFragmentBase {
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@ -34,4 +47,31 @@ public class LogDetailFragment extends I2PFragmentBase {
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_log_actions, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_copy_logs:
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(mEntry);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText(
|
||||
getString(R.string.i2p_android_logs), mEntry);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
Toast.makeText(getActivity(), R.string.logs_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,26 @@
|
||||
package net.i2p.android.router.log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import java.util.List;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LogFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<List<String>> {
|
||||
public static final String LOG_LEVEL = "log_level";
|
||||
@ -25,6 +34,7 @@ public class LogFragment extends ListFragment implements
|
||||
private static final int LEVEL_ALL = 2;
|
||||
|
||||
OnEntrySelectedListener mEntrySelectedCallback;
|
||||
private final List<String> mLogEntries = new ArrayList<>();
|
||||
private LogAdapter mAdapter;
|
||||
private TextView mHeaderView;
|
||||
private String mLogLevel;
|
||||
@ -34,6 +44,8 @@ public class LogFragment extends ListFragment implements
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
private boolean mActivateOnItemClick = false;
|
||||
|
||||
private MenuItem mCopyLogs;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnEntrySelectedListener {
|
||||
public void onEntrySelected(String entry);
|
||||
@ -62,6 +74,12 @@ public class LogFragment extends ListFragment implements
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@ -122,6 +140,53 @@ public class LogFragment extends ListFragment implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.fragment_log_actions, menu);
|
||||
mCopyLogs = menu.findItem(R.id.action_copy_logs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
mCopyLogs.setVisible(I2PAppContext.getCurrentContext() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_copy_logs:
|
||||
String logText = "";
|
||||
synchronized (mLogEntries) {
|
||||
for (String logEntry : mLogEntries) {
|
||||
logText += logEntry;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isError = "ERROR".equals(mLogLevel);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(logText);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText(
|
||||
isError ? getString(R.string.i2p_android_error_logs) : getString(R.string.i2p_android_logs),
|
||||
logText);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
int textId;
|
||||
if (isError)
|
||||
textId = R.string.error_logs_copied_to_clipboard;
|
||||
else
|
||||
textId = R.string.logs_copied_to_clipboard;
|
||||
Toast.makeText(getActivity(), textId, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
@ -167,6 +232,10 @@ public class LogFragment extends ListFragment implements
|
||||
List<String> data) {
|
||||
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
|
||||
LEVEL_ERROR : LEVEL_ALL)) {
|
||||
synchronized (mLogEntries) {
|
||||
mLogEntries.clear();
|
||||
mLogEntries.addAll(data);
|
||||
}
|
||||
mAdapter.setData(data);
|
||||
String header = getHeader(data.size(), ("ERROR".equals(mLogLevel)));
|
||||
mHeaderView.setText(header);
|
||||
|
@ -1,14 +1,16 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.data.Hash;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBar.Tab;
|
||||
|
||||
public class NetDbActivity extends I2PActivityBase implements
|
||||
NetDbListFragment.OnEntrySelectedListener {
|
||||
@ -18,7 +20,11 @@ public class NetDbActivity extends I2PActivityBase implements
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
private static final String SELECTED_TAB = "selected_tab";
|
||||
private static final String SELECTED_PAGE = "selected_page";
|
||||
private static final int PAGE_STATS = 0;
|
||||
private static final int PAGE_ROUTERS = 1;
|
||||
|
||||
private Spinner mSpinner;
|
||||
|
||||
@Override
|
||||
protected boolean canUseTwoPanes() {
|
||||
@ -29,41 +35,22 @@ public class NetDbActivity extends I2PActivityBase implements
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set up action bar for tabs
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
// Statistics tab
|
||||
NetDbSummaryPagerFragment sf = new NetDbSummaryPagerFragment();
|
||||
actionBar.addTab(
|
||||
actionBar.newTab()
|
||||
.setText("Statistics")
|
||||
.setTabListener(new NetDbSummaryPagerTabListener(sf)));
|
||||
mSpinner.setAdapter(ArrayAdapter.createFromResource(this,
|
||||
R.array.netdb_pages, android.R.layout.simple_spinner_dropdown_item));
|
||||
|
||||
// Routers tab
|
||||
NetDbListFragment rf = new NetDbListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, true);
|
||||
rf.setArguments(args);
|
||||
actionBar.addTab(
|
||||
actionBar.newTab()
|
||||
.setText("Routers")
|
||||
.setTabListener(new TabListener(rf)));
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectPage(i);
|
||||
}
|
||||
|
||||
// LeaseSets tab
|
||||
NetDbListFragment lf = new NetDbListFragment();
|
||||
args = new Bundle();
|
||||
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, false);
|
||||
lf.setArguments(args);
|
||||
actionBar.addTab(
|
||||
actionBar.newTab()
|
||||
.setText("LeaseSets")
|
||||
.setTabListener(new TabListener(lf)));
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_TAB);
|
||||
actionBar.setSelectedNavigationItem(selected);
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (findViewById(R.id.detail_fragment) != null) {
|
||||
// The detail container view will be present only in the
|
||||
@ -71,38 +58,37 @@ public class NetDbActivity extends I2PActivityBase implements
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
rf.setActivateOnItemClick(true);
|
||||
lf.setActivateOnItemClick(true);
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_PAGE);
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectPage(PAGE_STATS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_TAB,
|
||||
getSupportActionBar().getSelectedNavigationIndex());
|
||||
outState.putInt(SELECTED_PAGE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
public static class NetDbSummaryPagerTabListener extends TabListener {
|
||||
public NetDbSummaryPagerTabListener(Fragment fragment) {
|
||||
super(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabSelected(Tab tab, FragmentTransaction ft) {
|
||||
/**
|
||||
* This is a work-around for Issue 42601
|
||||
* https://code.google.com/p/android/issues/detail?id=42601
|
||||
*
|
||||
* The method getChildFragmentManager() does not clear up
|
||||
* when the Fragment is detached.
|
||||
*/
|
||||
mFragment = new NetDbSummaryPagerFragment();
|
||||
super.onTabSelected(tab, ft);
|
||||
private void selectPage(int page) {
|
||||
Fragment f;
|
||||
if (page == PAGE_STATS)
|
||||
f = new NetDbSummaryPagerFragment();
|
||||
else {
|
||||
f = new NetDbListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(NetDbListFragment.SHOW_ROUTERS, page == PAGE_ROUTERS);
|
||||
f.setArguments(args);
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
if (mTwoPane)
|
||||
((NetDbListFragment) f).setActivateOnItemClick(true);
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f).commit();
|
||||
}
|
||||
|
||||
// NetDbListFragment.OnEntrySelectedListener
|
||||
@ -115,12 +101,12 @@ public class NetDbActivity extends I2PActivityBase implements
|
||||
NetDbDetailFragment detailFrag = NetDbDetailFragment.newInstance(
|
||||
isRouterInfo, entryHash);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
.replace(R.id.detail_fragment, detailFrag).commit();
|
||||
|
||||
// If we are coming from a LS to a RI, change the tab
|
||||
int currentTab = getSupportActionBar().getSelectedNavigationIndex();
|
||||
if (isRouterInfo && currentTab !=1)
|
||||
getSupportActionBar().setSelectedNavigationItem(1);
|
||||
int currentTab = mSpinner.getSelectedItemPosition();
|
||||
if (isRouterInfo && currentTab != PAGE_ROUTERS)
|
||||
selectPage(PAGE_ROUTERS);
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
|
@ -21,8 +21,8 @@ import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -7,7 +7,7 @@ import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
|
||||
private RouterContext mRContext;
|
||||
private boolean mRouters;
|
||||
@ -44,17 +45,17 @@ public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
|
||||
|
||||
@Override
|
||||
public List<NetDbEntry> loadInBackground() {
|
||||
List<NetDbEntry> ret = new ArrayList<NetDbEntry>();
|
||||
List<NetDbEntry> ret = new ArrayList<>();
|
||||
if (mRContext.netDb().isInitialized()) {
|
||||
if (mRouters) {
|
||||
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
|
||||
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
|
||||
routers.addAll(mRContext.netDb().getRouters());
|
||||
for (RouterInfo ri : routers) {
|
||||
NetDbEntry entry = NetDbEntry.fromRouterInfo(mRContext, ri);
|
||||
ret.add(entry);
|
||||
}
|
||||
} else {
|
||||
Set<LeaseSet> leases = new TreeSet<LeaseSet>(new LeaseSetComparator());
|
||||
Set<LeaseSet> leases = new TreeSet<>(new LeaseSetComparator());
|
||||
leases.addAll(mRContext.netDb().getLeases());
|
||||
for (LeaseSet ls : leases) {
|
||||
NetDbEntry entry = NetDbEntry.fromLeaseSet(mRContext, ls);
|
||||
|
@ -1,19 +1,20 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>>> {
|
||||
private RouterContext mRContext;
|
||||
private List<ObjectCounter<String>> mData;
|
||||
@ -31,16 +32,16 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
|
||||
|
||||
@Override
|
||||
public List<ObjectCounter<String>> loadInBackground() {
|
||||
List<ObjectCounter<String>> ret = new ArrayList<ObjectCounter<String>>();
|
||||
List<ObjectCounter<String>> ret = new ArrayList<>();
|
||||
|
||||
ObjectCounter<String> versions = new ObjectCounter<String>();
|
||||
ObjectCounter<String> countries = new ObjectCounter<String>();
|
||||
ObjectCounter<String> transports = new ObjectCounter<String>();
|
||||
ObjectCounter<String> versions = new ObjectCounter<>();
|
||||
ObjectCounter<String> countries = new ObjectCounter<>();
|
||||
ObjectCounter<String> transports = new ObjectCounter<>();
|
||||
|
||||
if (mRContext.netDb().isInitialized()) {
|
||||
if (mRContext != null && mRContext.netDb() != null && mRContext.netDb().isInitialized()) {
|
||||
Hash us = mRContext.routerHash();
|
||||
|
||||
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
|
||||
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
|
||||
routers.addAll(mRContext.netDb().getRouters());
|
||||
for (RouterInfo ri : routers) {
|
||||
Hash key = ri.getHash();
|
||||
|
@ -1,12 +1,5 @@
|
||||
package net.i2p.android.router.netdb;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.VersionComparator;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
@ -16,6 +9,14 @@ import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class NetDbSummaryTableFragment extends Fragment {
|
||||
private static final String CATEGORY = "category";
|
||||
private static final String COUNTS = "counts";
|
||||
@ -44,7 +45,7 @@ public class NetDbSummaryTableFragment extends Fragment {
|
||||
|
||||
mTable = (TableLayout) v.findViewById(R.id.table);
|
||||
|
||||
List<String> objects = new ArrayList<String>(mCounts.objects());
|
||||
List<String> objects = new ArrayList<>(mCounts.objects());
|
||||
if (!objects.isEmpty()) {
|
||||
createTableTitle();
|
||||
|
||||
|
@ -6,6 +6,12 @@ import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import net.i2p.android.apps.EepGetFetcher;
|
||||
import net.i2p.android.router.BuildConfig;
|
||||
import net.i2p.android.router.util.AppCache;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@ -13,9 +19,6 @@ import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.i2p.android.apps.EepGetFetcher;
|
||||
import net.i2p.android.router.util.AppCache;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
/**
|
||||
* Usage: content://net.i2p.android.router/NONCE/ENCODED-SCHEME/ENCODED-AUTHORITY/ENCODED_PATH + QUERY_MARKER + ENCODED-QUERY
|
||||
@ -45,7 +48,7 @@ public class CacheProvider extends ContentProvider {
|
||||
//private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt()));
|
||||
private static final String NONCE = "0";
|
||||
private static final String SCHEME = "content";
|
||||
public static final String AUTHORITY = "net.i2p.android";
|
||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
|
||||
/** includes the nonce */
|
||||
public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE);
|
||||
|
||||
@ -140,7 +143,7 @@ public class CacheProvider extends ContentProvider {
|
||||
*
|
||||
* @param uri must contain a scheme, authority and path with nonce etc. as defined above
|
||||
* @return non-null
|
||||
* @throws FNFE on error
|
||||
* @throws java.io.FileNotFoundException on error
|
||||
*/
|
||||
public static Uri getI2PUri(Uri uri) throws FileNotFoundException {
|
||||
String resPath = uri.getEncodedPath();
|
||||
@ -242,8 +245,7 @@ public class CacheProvider extends ContentProvider {
|
||||
if (file.length() > 0) {
|
||||
// this call will insert it back to us (don't set as current base)
|
||||
Uri content = cache.addCacheFile(uri, false);
|
||||
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return parcel;
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
} else {
|
||||
Util.d("CacheProvider Sucess but no data " + uri);
|
||||
}
|
||||
@ -275,7 +277,7 @@ public class CacheProvider extends ContentProvider {
|
||||
put(uri, fileURI);
|
||||
}
|
||||
Boolean setAsCurrentBase = values.getAsBoolean(CURRENT_BASE);
|
||||
if (setAsCurrentBase != null && setAsCurrentBase.booleanValue()) {
|
||||
if (setAsCurrentBase != null && setAsCurrentBase) {
|
||||
Util.d("CacheProvider set current base " + uri);
|
||||
setCurrentBase(uri);
|
||||
}
|
||||
@ -302,7 +304,7 @@ public class CacheProvider extends ContentProvider {
|
||||
|
||||
private void cleanup() {
|
||||
String pfx = CONTENT_URI.toString();
|
||||
List<String> toDelete = new ArrayList<String>();
|
||||
List<String> toDelete = new ArrayList<>();
|
||||
Map<String, ?> map = _sharedPrefs.getAll();
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
String path = (String) e.getValue();
|
||||
|
@ -11,6 +11,7 @@ import android.net.NetworkInfo;
|
||||
import android.os.IBinder;
|
||||
import net.i2p.android.router.service.RouterBinder;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
|
||||
public class I2PReceiver extends BroadcastReceiver {
|
||||
@ -33,7 +34,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
@SuppressWarnings("LeakingThisInConstructor")
|
||||
Intent registerReceiver = context.registerReceiver(this, intents);
|
||||
_wasConnected = Util.isConnected(context);
|
||||
_wasConnected = Connectivity.isConnected(context);
|
||||
}
|
||||
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -57,7 +58,7 @@ public class I2PReceiver extends BroadcastReceiver {
|
||||
|
||||
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
|
||||
action.equals(Intent.ACTION_TIME_TICK)) {
|
||||
boolean connected = Util.isConnected(context);
|
||||
boolean connected = Connectivity.isConnected(context);
|
||||
if (_wasConnected && !connected) {
|
||||
// notify + 2 timer ticks
|
||||
if (++_unconnectedCount >= 3) {
|
||||
|
@ -0,0 +1,25 @@
|
||||
package net.i2p.android.router.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import net.i2p.android.router.I2PConstants;
|
||||
import net.i2p.android.router.service.RouterService;
|
||||
|
||||
public class OnBootReceiver extends BroadcastReceiver implements I2PConstants {
|
||||
public static final String PREF_START_ON_BOOT = ANDROID_PREF_PREFIX + "startOnBoot";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean startOnBoot = prefs.getBoolean(PREF_START_ON_BOOT, false);
|
||||
|
||||
if (startOnBoot) {
|
||||
Intent routerService = new Intent(context, RouterService.class);
|
||||
context.startService(routerService);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import android.content.Context;
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
class Init {
|
||||
|
||||
private final Context ctx;
|
||||
@ -32,8 +34,7 @@ class Init {
|
||||
File tmp = new File(myDir, "tmp");
|
||||
File[] files = tmp.listFiles();
|
||||
if (files != null) {
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File f = files[i];
|
||||
for (File f : files) {
|
||||
Util.d("Deleting old file/dir " + f);
|
||||
FileUtil.rmdir(f, false);
|
||||
}
|
||||
|
@ -9,17 +9,9 @@ import android.os.Message;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.receiver.I2PReceiver;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Notifications;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -27,7 +19,8 @@ import net.i2p.router.Job;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.RouterLaunch;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Runs the router
|
||||
@ -58,7 +51,7 @@ public class RouterService extends Service {
|
||||
* that it can be accessed more efficiently from inner classes.
|
||||
*/
|
||||
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
|
||||
= new RemoteCallbackList<IRouterStateCallback>();
|
||||
= new RemoteCallbackList<>();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
@ -115,7 +108,7 @@ public class RouterService extends Service {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
_receiver = new I2PReceiver(this);
|
||||
if(Util.isConnected(this)) {
|
||||
if(Connectivity.isConnected(this)) {
|
||||
if(restart) {
|
||||
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting");
|
||||
} else {
|
||||
@ -149,7 +142,7 @@ public class RouterService extends Service {
|
||||
Util.d(MARKER + this + " waiter handler"
|
||||
+ " Current state is: " + _state);
|
||||
if(_state == State.WAITING) {
|
||||
if(Util.isConnected(RouterService.this)) {
|
||||
if(Connectivity.isConnected(RouterService.this)) {
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.WAITING) {
|
||||
return;
|
||||
@ -175,159 +168,18 @@ public class RouterService extends Service {
|
||||
//NativeBigInteger.main(null);
|
||||
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
|
||||
|
||||
|
||||
// Before we launch, fix up any settings that need to be fixed here.
|
||||
// This should be done in the core, but as of this writing it isn't!
|
||||
|
||||
// Step one. Load the propertites.
|
||||
Properties props = new OrderedProperties();
|
||||
Properties oldprops = new OrderedProperties();
|
||||
String wrapName = _myDir + "/router.config";
|
||||
try {
|
||||
InputStream fin = new FileInputStream(new File(wrapName));
|
||||
DataHelper.loadProps(props, fin);
|
||||
} catch(IOException ioe) {
|
||||
// shouldn't happen...
|
||||
}
|
||||
oldprops.putAll(props);
|
||||
// Step two, check for any port settings, and copy for those that are missing.
|
||||
int UDPinbound;
|
||||
int UDPinlocal;
|
||||
int TCPinbound;
|
||||
int TCPinlocal;
|
||||
UDPinbound = Integer.parseInt(props.getProperty("i2np.udp.port", "-1"));
|
||||
UDPinlocal = Integer.parseInt(props.getProperty("i2np.udp.internalPort", "-1"));
|
||||
TCPinbound = Integer.parseInt(props.getProperty("i2np.ntcp.port", "-1"));
|
||||
TCPinlocal = Integer.parseInt(props.getProperty("i2np.ntcp.internalPort", "-1"));
|
||||
boolean hasUDPinbound = UDPinbound != -1;
|
||||
boolean hasUDPinlocal = UDPinlocal != -1;
|
||||
boolean hasTCPinbound = TCPinbound != -1;
|
||||
boolean hasTCPinlocal = TCPinlocal != -1;
|
||||
|
||||
// check and clear values based on these:
|
||||
boolean udp = Boolean.parseBoolean(props.getProperty("i2np.udp.enable", "false"));
|
||||
boolean tcp = Boolean.parseBoolean(props.getProperty("i2np.ntcp.enable", "false"));
|
||||
|
||||
// Fix if both are false.
|
||||
if(!(udp || tcp)) {
|
||||
// If both are not on, turn them both on.
|
||||
props.setProperty("i2np.udp.enable", "true");
|
||||
props.setProperty("i2np.ntcp.enable", "true");
|
||||
}
|
||||
|
||||
// Fix if we have local but no inbound
|
||||
if(!hasUDPinbound && hasUDPinlocal) {
|
||||
// if we got a local port and no external port, set it
|
||||
hasUDPinbound = true;
|
||||
UDPinbound = UDPinlocal;
|
||||
}
|
||||
if(!hasTCPinbound && hasTCPinlocal) {
|
||||
// if we got a local port and no external port, set it
|
||||
hasTCPinbound = true;
|
||||
TCPinbound = TCPinlocal;
|
||||
}
|
||||
|
||||
boolean anyUDP = hasUDPinbound || hasUDPinlocal;
|
||||
boolean anyTCP = hasTCPinbound || hasTCPinlocal;
|
||||
boolean anyport = anyUDP || anyTCP;
|
||||
|
||||
if(!anyport) {
|
||||
// generate one for UDPinbound, and fall thru.
|
||||
// FIX ME: Possibly not the best but should be OK.
|
||||
Random generator = new Random(System.currentTimeMillis());
|
||||
UDPinbound = generator.nextInt(55500) + 10000;
|
||||
anyUDP = true;
|
||||
}
|
||||
|
||||
// Copy missing port numbers
|
||||
if(anyUDP && !anyTCP) {
|
||||
TCPinbound = UDPinbound;
|
||||
TCPinlocal = UDPinlocal;
|
||||
}
|
||||
if(anyTCP && !anyUDP) {
|
||||
UDPinbound = TCPinbound;
|
||||
UDPinlocal = TCPinlocal;
|
||||
}
|
||||
// reset for a retest.
|
||||
hasUDPinbound = UDPinbound != -1;
|
||||
hasUDPinlocal = UDPinlocal != -1;
|
||||
hasTCPinbound = TCPinbound != -1;
|
||||
hasTCPinlocal = TCPinlocal != -1;
|
||||
anyUDP = hasUDPinbound || hasUDPinlocal;
|
||||
anyTCP = hasTCPinbound || hasTCPinlocal;
|
||||
boolean checkAnyUDP = anyUDP && udp;
|
||||
boolean checkAnyTCP = anyTCP && tcp;
|
||||
|
||||
// Enable things that need to be enabled.
|
||||
// Disable anything that needs to be disabled.
|
||||
if(!checkAnyUDP && !checkAnyTCP) {
|
||||
// enable the one(s) with values.
|
||||
if(anyUDP) {
|
||||
udp = true;
|
||||
}
|
||||
if(anyTCP) {
|
||||
tcp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!udp) {
|
||||
props.setProperty("i2np.udp.enable", "false");
|
||||
props.remove("i2np.udp.port");
|
||||
props.remove("i2np.udp.internalPort");
|
||||
} else {
|
||||
props.setProperty("i2np.udp.enable", "true");
|
||||
if(hasUDPinbound) {
|
||||
props.setProperty("i2np.udp.port", Integer.toString(UDPinbound));
|
||||
} else {
|
||||
props.remove("i2np.udp.port");
|
||||
}
|
||||
if(hasUDPinlocal) {
|
||||
props.setProperty("i2np.udp.internalPort", Integer.toString(UDPinlocal));
|
||||
} else {
|
||||
props.remove("i2np.udp.internalPort");
|
||||
}
|
||||
}
|
||||
|
||||
if(!tcp) {
|
||||
props.setProperty("i2np.ntcp.enable", "false");
|
||||
props.remove("i2np.ntcp.port");
|
||||
props.remove("i2np.ntcp.internalPort");
|
||||
} else {
|
||||
props.setProperty("i2np.ntcp.enable", "true");
|
||||
if(hasTCPinbound) {
|
||||
props.setProperty("i2np.ntcp.port", Integer.toString(TCPinbound));
|
||||
} else {
|
||||
props.remove("i2np.ntcp.port");
|
||||
}
|
||||
if(hasTCPinlocal) {
|
||||
props.setProperty("i2np.ntcp.internalPort", Integer.toString(TCPinlocal));
|
||||
} else {
|
||||
props.remove("i2np.ntcp.internalPort");
|
||||
}
|
||||
}
|
||||
// WHEW! Now test for any changes.
|
||||
if(!props.equals(oldprops)) {
|
||||
// save fixed properties.
|
||||
try {
|
||||
DataHelper.storeProps(props, new File(wrapName));
|
||||
} catch(IOException ioe) {
|
||||
// shouldn't happen...
|
||||
}
|
||||
}
|
||||
|
||||
// _NOW_ launch the router!
|
||||
// Launch the router!
|
||||
RouterLaunch.main(null);
|
||||
synchronized(_stateLock) {
|
||||
if(_state != State.STARTING) {
|
||||
return;
|
||||
}
|
||||
setState(State.RUNNING);
|
||||
List<?> contexts = RouterContext.listContexts();
|
||||
if((contexts == null) || (contexts.isEmpty())) {
|
||||
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
|
||||
_context = Util.getRouterContext();
|
||||
if (_context == null) {
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
}
|
||||
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
|
||||
_context = (RouterContext) contexts.get(0);
|
||||
_context.router().setKillVMOnEnd(false);
|
||||
Job loadJob = new LoadClientsJob(_context, _notif);
|
||||
_context.jobQueue().addJob(loadJob);
|
||||
@ -457,7 +309,7 @@ public class RouterService extends Service {
|
||||
|
||||
// ******** following methods may be accessed from Activities and Receivers ************
|
||||
/**
|
||||
* @returns null if router is not running
|
||||
* @return null if router is not running
|
||||
*/
|
||||
public RouterContext getRouterContext() {
|
||||
RouterContext rv = _context;
|
||||
|
@ -1,14 +1,15 @@
|
||||
package net.i2p.android.router.service;
|
||||
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
|
||||
public class StatSummarizer implements Runnable {
|
||||
private final RouterContext _context;
|
||||
private final List<SummaryListener> _listeners;
|
||||
@ -18,15 +19,20 @@ public class StatSummarizer implements Runnable {
|
||||
private Thread _thread;
|
||||
|
||||
public StatSummarizer() {
|
||||
_context = RouterContext.listContexts().get(0);
|
||||
_listeners = new CopyOnWriteArrayList<SummaryListener>();
|
||||
_context = Util.getRouterContext();
|
||||
_listeners = new CopyOnWriteArrayList<>();
|
||||
_instance = this;
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
if (_context != null)
|
||||
_context.addShutdownTask(new Shutdown());
|
||||
}
|
||||
|
||||
public static StatSummarizer instance() { return _instance; }
|
||||
|
||||
public void run() {
|
||||
// We can't do anything without a RouterContext
|
||||
if (_context == null)
|
||||
return;
|
||||
|
||||
_thread = Thread.currentThread();
|
||||
String specs = "";
|
||||
while (_isRunning && _context.router().isAlive()) {
|
||||
@ -103,7 +109,7 @@ public class StatSummarizer implements Runnable {
|
||||
*/
|
||||
List<Rate> parseSpecs(String specs) {
|
||||
StringTokenizer tok = new StringTokenizer(specs, ",");
|
||||
List<Rate> rv = new ArrayList<Rate>();
|
||||
List<Rate> rv = new ArrayList<>();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String spec = tok.nextToken();
|
||||
int split = spec.lastIndexOf('.');
|
||||
@ -111,7 +117,7 @@ public class StatSummarizer implements Runnable {
|
||||
continue;
|
||||
String name = spec.substring(0, split);
|
||||
String per = spec.substring(split+1);
|
||||
long period = -1;
|
||||
long period;
|
||||
try {
|
||||
period = Long.parseLong(per);
|
||||
RateStat rs = _context.statManager().getRate(name);
|
||||
|
@ -1,8 +1,17 @@
|
||||
package net.i2p.android.router.stats;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import net.i2p.android.router.I2PActivityBase;
|
||||
import net.i2p.android.router.R;
|
||||
@ -10,20 +19,17 @@ import net.i2p.android.router.SettingsActivity;
|
||||
import net.i2p.android.router.service.StatSummarizer;
|
||||
import net.i2p.android.router.service.SummaryListener;
|
||||
import net.i2p.stat.Rate;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class RateGraphActivity extends I2PActivityBase {
|
||||
private static final String SELECTED_RATE = "selected_rate";
|
||||
|
||||
private String[] mRates;
|
||||
private long[] mPeriods;
|
||||
private Spinner mSpinner;
|
||||
private boolean mFinishOnResume;
|
||||
|
||||
@Override
|
||||
@ -35,13 +41,13 @@ public class RateGraphActivity extends I2PActivityBase {
|
||||
if (StatSummarizer.instance() != null) {
|
||||
// Get the rates currently being graphed
|
||||
List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
|
||||
TreeSet<SummaryListener> ordered = new TreeSet<SummaryListener>(new AlphaComparator());
|
||||
TreeSet<SummaryListener> ordered = new TreeSet<>(new AlphaComparator());
|
||||
ordered.addAll(listeners);
|
||||
|
||||
if (ordered.size() > 0) {
|
||||
// Extract the rates and periods
|
||||
final String[] mRates = new String[ordered.size()];
|
||||
final long[] mPeriods = new long[ordered.size()];
|
||||
mRates = new String[ordered.size()];
|
||||
mPeriods = new long[ordered.size()];
|
||||
int i = 0;
|
||||
for (SummaryListener listener : ordered) {
|
||||
Rate r = listener.getRate();
|
||||
@ -50,35 +56,31 @@ public class RateGraphActivity extends I2PActivityBase {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Set up action bar for drop-down list
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||
mSpinner = (Spinner) findViewById(R.id.main_spinner);
|
||||
mSpinner.setVisibility(View.VISIBLE);
|
||||
|
||||
SpinnerAdapter mSpinnerAdapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_dropdown_item, mRates);
|
||||
mSpinner.setAdapter(new ArrayAdapter<>(this,
|
||||
android.R.layout.simple_spinner_dropdown_item, mRates));
|
||||
|
||||
ActionBar.OnNavigationListener mNavigationListener = new ActionBar.OnNavigationListener() {
|
||||
String[] rates = mRates;
|
||||
long[] periods = mPeriods;
|
||||
|
||||
public boolean onNavigationItemSelected(int position, long itemId) {
|
||||
String rateName = rates[position];
|
||||
long period = periods[position];
|
||||
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f, rates[position]).commit();
|
||||
return true;
|
||||
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
selectRate(i);
|
||||
}
|
||||
};
|
||||
|
||||
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationListener);
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int selected = savedInstanceState.getInt(SELECTED_RATE);
|
||||
actionBar.setSelectedNavigationItem(selected);
|
||||
}
|
||||
mSpinner.setSelection(selected);
|
||||
} else
|
||||
selectRate(0);
|
||||
} else {
|
||||
DialogFragment df = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
@ -112,6 +114,7 @@ public class RateGraphActivity extends I2PActivityBase {
|
||||
}
|
||||
} else {
|
||||
DialogFragment df = new DialogFragment() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
@ -130,6 +133,14 @@ public class RateGraphActivity extends I2PActivityBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void selectRate(int position) {
|
||||
String rateName = mRates[position];
|
||||
long period = mPeriods[position];
|
||||
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.main_fragment, f, rateName).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@ -142,8 +153,8 @@ public class RateGraphActivity extends I2PActivityBase {
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_RATE,
|
||||
getSupportActionBar().getSelectedNavigationIndex());
|
||||
if (mSpinner != null)
|
||||
outState.putInt(SELECTED_RATE, mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
private static class AlphaComparator implements Comparator<SummaryListener> {
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.i2p.android.router.stats;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.androidplot.Plot;
|
||||
import com.androidplot.xy.BoundaryMode;
|
||||
import com.androidplot.xy.LineAndPointFormatter;
|
||||
@ -18,12 +20,13 @@ import net.i2p.android.router.R;
|
||||
import net.i2p.android.router.service.StatSummarizer;
|
||||
import net.i2p.android.router.service.SummaryListener;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
|
||||
public class RateGraphFragment extends I2PFragmentBase {
|
||||
// redraws a plot whenever an update is received:
|
||||
@ -131,8 +134,8 @@ public class RateGraphFragment extends I2PFragmentBase {
|
||||
_ratePlot.setRangeValueFormat(new Format() {
|
||||
|
||||
@Override
|
||||
public StringBuffer format(Object obj, StringBuffer toAppendTo,
|
||||
FieldPosition pos) {
|
||||
public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
|
||||
@NonNull FieldPosition pos) {
|
||||
double val = ((Number) obj).doubleValue();
|
||||
if (val >= 10 * 1000 * 1000)
|
||||
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
|
||||
@ -147,7 +150,7 @@ public class RateGraphFragment extends I2PFragmentBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseObject(String source, ParsePosition pos) {
|
||||
public Object parseObject(String source, @NonNull ParsePosition pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,9 @@ import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import net.i2p.android.router.provider.CacheProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -14,7 +17,6 @@ import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.i2p.android.router.provider.CacheProvider;
|
||||
|
||||
/**
|
||||
* A least recently used cache with a max number of entries
|
||||
@ -32,7 +34,7 @@ public class AppCache {
|
||||
/** the LRU cache */
|
||||
private final Map<Integer, Object> _cache;
|
||||
|
||||
private static final Integer DUMMY = Integer.valueOf(0);
|
||||
private static final Integer DUMMY = 0;
|
||||
private static final String DIR_NAME = "appCache";
|
||||
/** fragment into this many subdirectories */
|
||||
private static final int NUM_DIRS = 32;
|
||||
@ -89,7 +91,7 @@ public class AppCache {
|
||||
public Uri addCacheFile(Uri key, boolean setAsCurrentBase) {
|
||||
int hash = toHash(key);
|
||||
synchronized(_cache) {
|
||||
_cache.put(Integer.valueOf(hash), DUMMY);
|
||||
_cache.put(hash, DUMMY);
|
||||
}
|
||||
// file:/// uri
|
||||
//return Uri.fromFile(toFile(hash)).toString();
|
||||
@ -104,7 +106,7 @@ public class AppCache {
|
||||
public void removeCacheFile(Uri key) {
|
||||
int hash = toHash(key);
|
||||
synchronized(_cache) {
|
||||
_cache.remove(Integer.valueOf(hash));
|
||||
_cache.remove(hash);
|
||||
}
|
||||
deleteContent(key);
|
||||
}
|
||||
@ -121,7 +123,7 @@ public class AppCache {
|
||||
// poke the LRU
|
||||
Object present;
|
||||
synchronized(_cache) {
|
||||
present = _cache.get(Integer.valueOf(hash));
|
||||
present = _cache.get(hash);
|
||||
}
|
||||
if (present != null)
|
||||
setAsCurrentBase(key);
|
||||
@ -173,17 +175,16 @@ public class AppCache {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null)
|
||||
return 0;
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File f = files[i];
|
||||
if (f.isDirectory()) {
|
||||
rv += enumerate(f, fileList);
|
||||
} else {
|
||||
long len = f.length();
|
||||
if (len > 0) {
|
||||
fileList.add(f);
|
||||
rv += len;
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
rv += enumerate(f, fileList);
|
||||
} else {
|
||||
long len = f.length();
|
||||
if (len > 0) {
|
||||
fileList.add(f);
|
||||
rv += len;
|
||||
} else {
|
||||
f.delete();
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,7 +196,7 @@ public class AppCache {
|
||||
try {
|
||||
int hash = toHash(f);
|
||||
synchronized(_cache) {
|
||||
_cache.put(Integer.valueOf(hash), DUMMY);
|
||||
_cache.put(hash, DUMMY);
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
Util.d("Huh bad file?" + iae);
|
||||
@ -294,7 +295,7 @@ public class AppCache {
|
||||
@Override
|
||||
public Object put(Integer key, Object value) {
|
||||
Object rv = super.put(key, value);
|
||||
File f = toFile(key.intValue());
|
||||
File f = toFile(key);
|
||||
if (f.exists()) {
|
||||
_totalSize += f.length();
|
||||
}
|
||||
@ -306,7 +307,7 @@ public class AppCache {
|
||||
public Object remove(Object key) {
|
||||
Object rv = super.remove(key);
|
||||
if ( /* rv != null && */ key instanceof Integer) {
|
||||
File f = toFile(((Integer)key).intValue());
|
||||
File f = toFile((Integer) key);
|
||||
if (f.exists()) {
|
||||
_totalSize -= f.length();
|
||||
f.delete();
|
||||
|
@ -0,0 +1,125 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public abstract class BetterAsyncTaskLoader<T> extends AsyncTaskLoader<T> {
|
||||
protected T mData;
|
||||
|
||||
public BetterAsyncTaskLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is new data to deliver to the client. The
|
||||
* super class will take care of delivering it; the implementation
|
||||
* here just adds a little more logic.
|
||||
*/
|
||||
@Override
|
||||
public void deliverResult(T data) {
|
||||
if (isReset()) {
|
||||
// An async query came in while the loader is stopped. We
|
||||
// don't need the result.
|
||||
if (data != null) {
|
||||
releaseResources(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Hold a reference to the old data so it doesn't get garbage collected.
|
||||
// We must protect it until the new data has been delivered.
|
||||
T oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted()) {
|
||||
// If the Loader is currently started, we can immediately
|
||||
// deliver its results.
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
// Invalidate the old data as we don't need it any more.
|
||||
if (oldData != null && oldData != data) {
|
||||
releaseResources(oldData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to start the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null) {
|
||||
// Deliver any previously loaded data immediately.
|
||||
deliverResult(mData);
|
||||
}
|
||||
|
||||
// Start watching for changes
|
||||
onStartMonitoring();
|
||||
|
||||
if (takeContentChanged() || mData == null) {
|
||||
// When the observer detects a change, it should call onContentChanged()
|
||||
// on the Loader, which will cause the next call to takeContentChanged()
|
||||
// to return true. If this is ever the case (or if the current data is
|
||||
// null), we force a new load.
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to stop the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
// The Loader is in a stopped state, so we should attempt to cancel the
|
||||
// current load (if there is one).
|
||||
cancelLoad();
|
||||
|
||||
// Note that we leave the observer as is. Loaders in a stopped state
|
||||
// should still monitor the data source for changes so that the Loader
|
||||
// will know to force a new load if it is ever started again.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to completely reset the Loader.
|
||||
*/
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader has been stopped.
|
||||
onStopLoading();
|
||||
|
||||
// At this point we can release the resources associated with 'mData'.
|
||||
if (mData != null) {
|
||||
releaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
|
||||
// Stop monitoring for changes.
|
||||
onStopMonitoring();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a request to cancel a load.
|
||||
*/
|
||||
@Override
|
||||
public void onCanceled(T data) {
|
||||
// Attempt to cancel the current asynchronous load.
|
||||
super.onCanceled(data);
|
||||
|
||||
// The load has been canceled, so we should release the resources
|
||||
// associated with 'data'.
|
||||
releaseResources(data);
|
||||
}
|
||||
|
||||
protected abstract void onStartMonitoring();
|
||||
protected abstract void onStopMonitoring();
|
||||
|
||||
/**
|
||||
* Helper function to take care of releasing resources associated
|
||||
* with an actively loaded data set.
|
||||
* For a simple List, there is nothing to do. For something like a Cursor, we
|
||||
* would close it in this method. All resources associated with the Loader
|
||||
* should be released here.
|
||||
*/
|
||||
protected abstract void releaseResources(T data);
|
||||
}
|
134
app/src/main/java/net/i2p/android/router/util/Connectivity.java
Normal file
@ -0,0 +1,134 @@
|
||||
// License: MIT
|
||||
// http://opensource.org/licenses/MIT
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
/**
|
||||
* Check device's network connectivity and speed.
|
||||
*
|
||||
* @author emil http://stackoverflow.com/users/220710/emil
|
||||
* @author str4d
|
||||
*/
|
||||
public class Connectivity {
|
||||
/**
|
||||
* Get the network info.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return the active NetworkInfo.
|
||||
*/
|
||||
public static NetworkInfo getNetworkInfo(Context context) {
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm.getActiveNetworkInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any connectivity at all.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we are connected to a network, false otherwise.
|
||||
*/
|
||||
public static boolean isConnected(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
// Works on emulator and devices.
|
||||
// Note the use of isAvailable() - without this, isConnected() can
|
||||
// return true when Wifi is disabled.
|
||||
// http://stackoverflow.com/a/2937915
|
||||
return (info != null && info.isAvailable() && info.isConnected());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any connectivity to a Wifi network.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we are connected to a Wifi network, false otherwise.
|
||||
*/
|
||||
public static boolean isConnectedWifi(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
return (info != null && info.isAvailable() && info.isConnected() &&
|
||||
info.getType() == ConnectivityManager.TYPE_WIFI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any connectivity to a mobile network.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we are connected to a mobile network, false otherwise.
|
||||
*/
|
||||
public static boolean isConnectedMobile(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
return (info != null && info.isAvailable() && info.isConnected() &&
|
||||
info.getType() == ConnectivityManager.TYPE_MOBILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is fast connectivity.
|
||||
*
|
||||
* @param context the Context.
|
||||
* @return true if we have "fast" connectivity, false otherwise.
|
||||
*/
|
||||
public static boolean isConnectedFast(Context context) {
|
||||
NetworkInfo info = Connectivity.getNetworkInfo(context);
|
||||
return (info != null && info.isAvailable() && info.isConnected() &&
|
||||
Connectivity.isConnectionFast(info.getType(), info.getSubtype()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the connection is fast.
|
||||
*
|
||||
* @param type the network type.
|
||||
* @param subType the network subtype.
|
||||
* @return true if the provided type/subtype combination is classified as fast.
|
||||
*/
|
||||
public static boolean isConnectionFast(int type, int subType) {
|
||||
if (type == ConnectivityManager.TYPE_WIFI) {
|
||||
return true;
|
||||
} else if (type == ConnectivityManager.TYPE_MOBILE) {
|
||||
switch (subType) {
|
||||
case TelephonyManager.NETWORK_TYPE_1xRTT:
|
||||
return false; // ~ 50-100 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_CDMA:
|
||||
return false; // ~ 14-64 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_EDGE:
|
||||
return false; // ~ 50-100 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_0:
|
||||
return true; // ~ 400-1000 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_A:
|
||||
return true; // ~ 600-1400 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_GPRS:
|
||||
return false; // ~ 100 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSDPA:
|
||||
return true; // ~ 2-14 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSPA:
|
||||
return true; // ~ 700-1700 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSUPA:
|
||||
return true; // ~ 1-23 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_UMTS:
|
||||
return true; // ~ 400-7000 kbps
|
||||
/*
|
||||
* Above API level 7, make sure to set android:targetSdkVersion
|
||||
* to appropriate level to use these
|
||||
*/
|
||||
case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11
|
||||
return true; // ~ 1-2 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9
|
||||
return true; // ~ 5 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13
|
||||
return true; // ~ 10-20 Mbps
|
||||
case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8
|
||||
return false; // ~25 kbps
|
||||
case TelephonyManager.NETWORK_TYPE_LTE: // API level 11
|
||||
return true; // ~ 10+ Mbps
|
||||
// Unknown
|
||||
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class IntEditTextPreference extends EditTextPreference {
|
||||
|
||||
public IntEditTextPreference(Context context) {
|
||||
super(context);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
public IntEditTextPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return String.format((String) super.getSummary(), getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
return String.valueOf(getPersistedInt(-1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
return persistInt(Integer.valueOf(value));
|
||||
}
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import java.net.IDN;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
@ -18,6 +14,10 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
import java.net.IDN;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class NamingServiceUtil {
|
||||
private static final String DEFAULT_NS = "BlockfileNamingService";
|
||||
|
||||
@ -106,7 +106,7 @@ public class NamingServiceUtil {
|
||||
* Ref: java.net.IDN and RFC 3940
|
||||
* @param host will be converted to lower case
|
||||
* @return name converted to lower case and punycoded if necessary
|
||||
* @throws IAE on various errors or if IDN is needed but not available
|
||||
* @throws java.lang.IllegalArgumentException on various errors or if IDN is needed but not available
|
||||
* @since 0.8.7
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
|
@ -0,0 +1,60 @@
|
||||
package net.i2p.android.router.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
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
|
||||
public CharSequence getSummary() {
|
||||
int port = getPersistedInt(-1);
|
||||
if (port < 0)
|
||||
return getContext().getResources().getString(R.string.unset);
|
||||
else
|
||||
return String.valueOf(port);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
int port = getPersistedInt(-1);
|
||||
if (port < 0)
|
||||
return defaultReturnValue;
|
||||
else
|
||||
return String.valueOf(port);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
if (value == null || value.isEmpty())
|
||||
return persistInt(-1);
|
||||
|
||||
int port;
|
||||
try {
|
||||
port = Integer.valueOf(value);
|
||||
if (port < 0)
|
||||
port = -1;
|
||||
} catch (NumberFormatException e) {
|
||||
port = -1;
|
||||
}
|
||||
|
||||
return persistInt(port);
|
||||
}
|
||||
}
|
@ -4,23 +4,31 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Build;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.android.router.I2PConstants;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.TransportManager;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public abstract class Util {
|
||||
private static final boolean _isEmulator = Build.MODEL.equals("sdk");
|
||||
|
||||
public abstract class Util implements I2PConstants {
|
||||
public static String getOurVersion(Context ctx) {
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
String us = ctx.getPackageName();
|
||||
@ -36,18 +44,17 @@ public abstract class Util {
|
||||
return "??";
|
||||
}
|
||||
|
||||
public static boolean isConnected(Context ctx) {
|
||||
// emulator always returns null NetworkInfo
|
||||
if (_isEmulator)
|
||||
return true;
|
||||
NetworkInfo current = getNetworkInfo(ctx);
|
||||
return current != null && current.isConnected();
|
||||
}
|
||||
|
||||
public static NetworkInfo getNetworkInfo(Context ctx) {
|
||||
ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo current = cm.getActiveNetworkInfo();
|
||||
return current;
|
||||
/**
|
||||
* Get the active RouterContext.
|
||||
*
|
||||
* @return the active RouterContext, or null
|
||||
*/
|
||||
public static RouterContext getRouterContext() {
|
||||
List<RouterContext> contexts = RouterContext.listContexts();
|
||||
if ( !((contexts == null) || (contexts.isEmpty())) ) {
|
||||
return contexts.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final String ANDROID_TAG = "I2P";
|
||||
@ -119,44 +126,46 @@ public abstract class Util {
|
||||
}
|
||||
}
|
||||
|
||||
/** copied from various private components */
|
||||
final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
|
||||
final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
|
||||
|
||||
public static List<Properties> getPropertiesFromPreferences(Context context) {
|
||||
List<Properties> pList = new ArrayList<Properties>();
|
||||
List<Properties> pList = new ArrayList<>();
|
||||
|
||||
// Copy prefs
|
||||
Properties routerProps = new OrderedProperties();
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// List to store stats for graphing
|
||||
List<String> statSummaries = new ArrayList<String>();
|
||||
List<String> statSummaries = new ArrayList<>();
|
||||
|
||||
// Properties to remove
|
||||
Properties toRemove = new OrderedProperties();
|
||||
|
||||
// List to store Log settings
|
||||
Properties logSettings = new OrderedProperties();
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
Map<String, ?> all = preferences.getAll();
|
||||
Iterator<String> iterator = all.keySet().iterator();
|
||||
// get values from the Map and make them strings.
|
||||
// This loop avoids needing to convert each one, or even know it's type, or if it exists yet.
|
||||
while (iterator.hasNext()) {
|
||||
String x = iterator.next();
|
||||
if ( x.startsWith("i2pandroid.")) // Skip over UI-related I2P Android settings
|
||||
continue;
|
||||
else if ( x.startsWith("stat.summaries.")) {
|
||||
for (String x : all.keySet()) {
|
||||
if (x.startsWith("stat.summaries.")) {
|
||||
String stat = x.substring("stat.summaries.".length());
|
||||
String checked = all.get(x).toString();
|
||||
if (checked.equals("true")) {
|
||||
statSummaries.add(stat);
|
||||
}
|
||||
} else if ( x.startsWith("logger.")) {
|
||||
} else if (x.startsWith("logger.")) {
|
||||
logSettings.put(x, all.get(x).toString());
|
||||
} else if (
|
||||
x.equals("router.hiddenMode") ||
|
||||
x.equals("i2cp.disableInterface")) {
|
||||
x.equals("i2cp.disableInterface")) {
|
||||
// special exception, we must invert the bool for these properties only.
|
||||
String string = all.get(x).toString();
|
||||
String inverted = Boolean.toString(!Boolean.parseBoolean(string));
|
||||
routerProps.setProperty(x, inverted);
|
||||
} else {
|
||||
} else if (!x.startsWith(ANDROID_PREF_PREFIX)) { // Skip over UI-related I2P Android settings
|
||||
String string = all.get(x).toString();
|
||||
routerProps.setProperty(x, string);
|
||||
}
|
||||
@ -172,9 +181,161 @@ public abstract class Util {
|
||||
routerProps.setProperty("stat.summaries", buf.toString());
|
||||
}
|
||||
|
||||
// See net.i2p.router.web.ConfigNetHandler.saveChanges()
|
||||
int udpPort = Integer.parseInt(routerProps.getProperty(UDPTransport.PROP_INTERNAL_PORT, "-1"));
|
||||
if (udpPort <= 0)
|
||||
routerProps.remove(UDPTransport.PROP_INTERNAL_PORT);
|
||||
int ntcpPort = Integer.parseInt(routerProps.getProperty(PROP_I2NP_NTCP_PORT, "-1"));
|
||||
boolean ntcpAutoPort = Boolean.parseBoolean(
|
||||
routerProps.getProperty(PROP_I2NP_NTCP_AUTO_PORT, "true"));
|
||||
if (ntcpPort <= 0 || ntcpAutoPort) {
|
||||
routerProps.remove(PROP_I2NP_NTCP_PORT);
|
||||
toRemove.setProperty(PROP_I2NP_NTCP_PORT, "");
|
||||
}
|
||||
|
||||
pList.add(routerProps);
|
||||
pList.add(toRemove);
|
||||
pList.add(logSettings);
|
||||
|
||||
return pList;
|
||||
}
|
||||
|
||||
// propName -> defaultValue
|
||||
private static HashMap<String, Boolean> booleanOptionsRequiringRestart = new HashMap<>();
|
||||
private static HashMap<String, String> stringOptionsRequiringRestart = new HashMap<>();
|
||||
static {
|
||||
HashMap<String, Boolean> boolToAdd = new HashMap<>();
|
||||
HashMap<String, String> strToAdd = new HashMap<>();
|
||||
|
||||
boolToAdd.put(TransportManager.PROP_ENABLE_UPNP, true);
|
||||
boolToAdd.put(TransportManager.PROP_ENABLE_NTCP, true);
|
||||
boolToAdd.put(TransportManager.PROP_ENABLE_UDP, true);
|
||||
boolToAdd.put(PROP_I2NP_NTCP_AUTO_PORT, true);
|
||||
boolToAdd.put(Router.PROP_HIDDEN, false);
|
||||
|
||||
strToAdd.put(UDPTransport.PROP_INTERNAL_PORT, "-1");
|
||||
strToAdd.put(PROP_I2NP_NTCP_PORT, "-1");
|
||||
|
||||
booleanOptionsRequiringRestart.putAll(boolToAdd);
|
||||
stringOptionsRequiringRestart.putAll(strToAdd);
|
||||
}
|
||||
/**
|
||||
* This function performs two tasks:
|
||||
* <ul><li>
|
||||
* The Properties object is modified to ensure that all options are valid
|
||||
* for the current state of the Android device (e.g. what type of network
|
||||
* the device is connected to).
|
||||
* </li><li>
|
||||
* The Properties object is checked to determine whether any options have
|
||||
* changed that will require a router restart.
|
||||
* </li></ul>
|
||||
*
|
||||
* @param props a Properties object containing the router.config
|
||||
* @param toRemove a Collection of properties that will be removed
|
||||
* @return true if the router needs to be restarted.
|
||||
*/
|
||||
public static boolean checkAndCorrectRouterConfig(Context context, Properties props, Collection<String> toRemove) {
|
||||
// Disable UPnP on mobile networks, ignoring user's configuration
|
||||
// TODO disabled until changes elsewhere are finished
|
||||
//if (Connectivity.isConnectedMobile(context)) {
|
||||
// props.setProperty(TransportManager.PROP_ENABLE_UPNP, Boolean.toString(false));
|
||||
//}
|
||||
|
||||
// Now check if a restart is required
|
||||
boolean restartRequired = false;
|
||||
RouterContext rCtx = getRouterContext();
|
||||
if (rCtx != null) {
|
||||
for (Map.Entry<String, Boolean> option : booleanOptionsRequiringRestart.entrySet()) {
|
||||
String propName = option.getKey();
|
||||
boolean defaultValue = option.getValue();
|
||||
boolean currentValue = defaultValue ? rCtx.getBooleanPropertyDefaultTrue(propName) : rCtx.getBooleanProperty(propName);
|
||||
boolean newValue = Boolean.parseBoolean(props.getProperty(propName, Boolean.toString(defaultValue)));
|
||||
restartRequired |= (currentValue != newValue);
|
||||
}
|
||||
if (!restartRequired) { // Cut out now if we already know the answer
|
||||
for (Map.Entry<String, String> option : stringOptionsRequiringRestart.entrySet()) {
|
||||
String propName = option.getKey();
|
||||
String defaultValue = option.getValue();
|
||||
String currentValue = rCtx.getProperty(propName, defaultValue);
|
||||
String newValue = props.getProperty(propName, defaultValue);
|
||||
restartRequired |= !currentValue.equals(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return restartRequired;
|
||||
}
|
||||
|
||||
public static String getFileDir(Context context) {
|
||||
// This needs to be changed so that we can have an alternative place
|
||||
return context.getFilesDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write properties to a file. If the file does not exist, it is created.
|
||||
* If the properties already exist in the file, they are updated.
|
||||
*
|
||||
* @param dir the file directory
|
||||
* @param file relative to dir
|
||||
* @param props properties to set
|
||||
*/
|
||||
public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) {
|
||||
mergeResourceToFile(ctx, dir, file, 0, props, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load defaults from resource, then add props from settings, and write back.
|
||||
* If resID is 0, defaults are not written over the existing file content.
|
||||
*
|
||||
* @param dir the file directory
|
||||
* @param file relative to dir
|
||||
* @param resID the ID of the default resource, or 0
|
||||
* @param userProps local properties or null
|
||||
* @param toRemove properties to remove, or null
|
||||
*/
|
||||
public static void mergeResourceToFile(Context ctx, String dir, String file, int resID,
|
||||
Properties userProps, Collection<String> toRemove) {
|
||||
InputStream fin = null;
|
||||
InputStream in = null;
|
||||
|
||||
try {
|
||||
Properties props = new OrderedProperties();
|
||||
try {
|
||||
fin = new FileInputStream(new File(dir, file));
|
||||
DataHelper.loadProps(props, fin);
|
||||
if (resID > 0)
|
||||
Util.d("Merging resource into file " + file);
|
||||
else
|
||||
Util.d("Merging properties into file " + file);
|
||||
} catch (IOException ioe) {
|
||||
if (resID > 0)
|
||||
Util.d("Creating file " + file + " from resource");
|
||||
else
|
||||
Util.d("Creating file " + file + " from properties");
|
||||
}
|
||||
|
||||
// write in default settings
|
||||
if (resID > 0)
|
||||
in = ctx.getResources().openRawResource(resID);
|
||||
if (in != null)
|
||||
DataHelper.loadProps(props, in);
|
||||
|
||||
// override with user settings
|
||||
if (userProps != null)
|
||||
props.putAll(userProps);
|
||||
if (toRemove != null) {
|
||||
for (String key : toRemove) {
|
||||
props.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
File path = new File(dir, file);
|
||||
DataHelper.storeProps(props, path);
|
||||
Util.d("Saved " + props.size() +" properties in " + file);
|
||||
} catch (IOException ioe) {
|
||||
} catch (Resources.NotFoundException nfe) {
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import java.io.OutputStream;
|
||||
import net.i2p.android.apps.EepGetFetcher;
|
||||
import net.i2p.android.router.provider.CacheProvider;
|
||||
import net.i2p.android.router.util.AppCache;
|
||||
import net.i2p.android.router.util.Connectivity;
|
||||
import net.i2p.android.router.util.Util;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EepGet;
|
||||
@ -77,7 +78,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Util.isConnected(view.getContext())) {
|
||||
if (!Connectivity.isConnected(view.getContext())) {
|
||||
fail(view, "No Internet connection is available");
|
||||
return true;
|
||||
}
|
||||
@ -193,8 +194,7 @@ public class I2PWebViewClient extends WebViewClient {
|
||||
/**
|
||||
* This should always be a content url
|
||||
*/
|
||||
void deleteCurrentPageCache(WebView view) {
|
||||
String url = view.getUrl();
|
||||
void deleteCurrentPageCache(WebView view, String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
if (CONTENT.equals(uri.getScheme())) {
|
||||
try {
|
||||
|
@ -22,6 +22,7 @@ import net.i2p.android.router.R;
|
||||
public class WebFragment extends I2PFragmentBase {
|
||||
|
||||
private I2PWebViewClient _wvClient;
|
||||
private String _uriStr;
|
||||
|
||||
public final static String HTML_URI = "html_url";
|
||||
public final static String HTML_RESOURCE_ID = "html_resource_id";
|
||||
@ -48,9 +49,9 @@ public class WebFragment extends I2PFragmentBase {
|
||||
wv.getSettings().setBuiltInZoomControls(true);
|
||||
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
|
||||
wv.getSettings().setUseWideViewPort(true);
|
||||
String uriStr = getArguments().getString(HTML_URI);
|
||||
if (uriStr != null) {
|
||||
Uri uri = Uri.parse(uriStr);
|
||||
_uriStr = getArguments().getString(HTML_URI);
|
||||
if (_uriStr != null) {
|
||||
Uri uri = Uri.parse(_uriStr);
|
||||
//wv.getSettings().setLoadsImagesAutomatically(true);
|
||||
//wv.loadUrl(uri.toString());
|
||||
// go thru the client so .i2p will work too
|
||||
@ -115,14 +116,18 @@ public class WebFragment extends I2PFragmentBase {
|
||||
_wvClient.cancelAll();
|
||||
wv.stopLoading();
|
||||
String url = wv.getUrl();
|
||||
Uri uri = Uri.parse(url);
|
||||
// If a resource, _uriStr == null but url != null (resource loads don't fail)
|
||||
// If a URL, _uriStr != null and url might be null (if pageload failed)
|
||||
if (url != null && (!url.equals(_uriStr)))
|
||||
_uriStr = url;
|
||||
Uri uri = Uri.parse(_uriStr);
|
||||
if ("data".equals(uri.getScheme())) {
|
||||
// welcome page... or just do nothing ?
|
||||
wv.reload();
|
||||
} else {
|
||||
// wv.reload() doesn't call shouldOverrideUrlLoading(), so do it this way
|
||||
_wvClient.deleteCurrentPageCache(wv);
|
||||
_wvClient.shouldOverrideUrlLoading(wv, url);
|
||||
_wvClient.deleteCurrentPageCache(wv, _uriStr);
|
||||
_wvClient.shouldOverrideUrlLoading(wv, _uriStr);
|
||||
}
|
||||
return true;
|
||||
|
||||
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* <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) {
|
||||
if (where < 0) {
|
||||
return true;
|
||||
}
|
||||
return 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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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, 0 + result.getIntrinsicWidth(), 0 + 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, 0 + drawable.getIntrinsicWidth(), 0 + 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: 1.7 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_add_white_24dp.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
app/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png
Normal file
After Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_content_copy_white_24dp.png
Normal file
After Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_create_white_24dp.png
Normal file
After Width: | Height: | Size: 339 B |
BIN
app/src/main/res/drawable-hdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 246 B |
Before Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_menu_white_24dp.png
Normal file
After Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_open_in_browser_white_24dp.png
Normal file
After Width: | Height: | Size: 276 B |
BIN
app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
Normal file
After Width: | Height: | Size: 282 B |
BIN
app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
app/src/main/res/drawable-hdpi/ic_schedule_black_24dp.png
Normal file
After Width: | Height: | Size: 650 B |
BIN
app/src/main/res/drawable-hdpi/ic_search_white_24dp.png
Normal file
After Width: | Height: | Size: 504 B |
BIN
app/src/main/res/drawable-hdpi/ic_shop_white_24dp.png
Normal file
After Width: | Height: | Size: 331 B |