Compare commits

..

119 Commits

Author SHA1 Message Date
6352cd9412 Client library 0.5
i2p.i2p tag: i2p-0.9.18
2015-03-02 00:51:30 +00:00
db6c74b4b8 Updated translations 2015-03-02 00:02:40 +00:00
a8d699bea2 (Hopefully) prevent NPE 2015-03-01 23:49:13 +00:00
5d9cb0029f Fixed style API bug 2015-03-01 23:28:11 +00:00
51f7fca1ea Updated translations 2015-03-01 11:08:55 +00:00
c61ccd32ba UDP and NTCP ports, part 2 2015-02-28 12:36:59 +00:00
1debd64bc3 Pull property definitions into one location 2015-02-28 10:19:42 +00:00
561bcfe3fa New translations 2015-02-28 04:59:15 +00:00
1c1f77f5c5 Updated translations 2015-02-28 04:58:27 +00:00
420526a7c4 Updated changelog 2015-02-28 04:57:10 +00:00
99496be412 Settings options for UDP and NTCP ports 2015-02-28 04:49:18 +00:00
b6f1cdc769 Enable Java 1.7 features 2015-02-28 00:46:49 +00:00
75e4153f4e Fix stringOptionsRequiringRestart handling; add NTCP and SSU port parsing 2015-02-27 23:28:01 +00:00
eefa5b8064 Prep work for supporting removal of router.config properties 2015-02-27 22:52:39 +00:00
9d32e44547 Logger: Configurable flush interval
From i2p.i2p rev 2f451e3579eb5df2d316c1b507950d119d59a8da
2015-02-27 11:04:03 +00:00
42a0d552c7 Fixed potential leak 2015-02-27 10:45:42 +00:00
65848dd22b Fix NPE in AddressEntryLoader 2015-02-27 10:20:00 +00:00
610de188a4 Downgrade to Gradle tools 1.0.1
1.1.* cause "Cannot resolve symbol" errors in Android Studio, even though Gradle
can build the app.
2015-02-27 09:50:34 +00:00
f1cd3032c5 Updated FAB library 2015-02-27 01:54:29 +00:00
1a922ba04a Updated build tools 2015-02-27 01:54:17 +00:00
bdd59734ec Updated Android support libraries 2015-02-26 23:34:49 +00:00
fe162e4f5c Updated Android Gradle build tools 2015-02-26 23:34:31 +00:00
69e30e97b8 Updated TODO 2015-02-26 23:33:53 +00:00
513fbe0c9f Updated translations 2015-01-15 10:34:13 +00:00
de23a76e6b Option to start I2P on boot 2015-01-15 10:31:25 +00:00
c9936894d8 Updated TODO 2015-01-15 05:47:01 +00:00
cb6efd9ed8 Updated translations 2015-01-01 23:18:19 +00:00
ad245003bf Updated browser config instructions for Firefox and Orfox 2014-12-29 11:40:04 +00:00
9f27aedc49 Added postman's tunnels to default i2ptunnel.config 2014-12-27 21:43:00 +00:00
d8f883dce8 Feedback from zzz 2014-12-25 09:48:26 +00:00
7db1fbac94 Updated translations 2014-12-23 23:39:13 +00:00
19464124d6 Fixed NPE on browser refresh 2014-12-23 22:30:04 +00:00
5ba616facc Updated TODO (thx for testing zzz) 2014-12-22 00:22:54 +00:00
590a8183aa Fix settings menu Intents in debug build 2014-12-17 02:42:13 +00:00
9a45bbd18c 0.9.17.1
i2p.i2p tag: i2p-0.9.17
2014-12-14 05:15:16 +00:00
c5eedc0a5e Updated translations 2014-12-14 04:28:50 +00:00
715302c813 Upgraded floatingactionbutton to 1.3.0, dropped unused cardview-v7 2014-12-14 04:26:38 +00:00
3327ed722a I2PTunnel: Ask to install app for tunnel, if none are installed when opening 2014-12-12 13:59:02 +00:00
40afd69a54 Enable debug versions to be installed alongside release versions 2014-12-12 13:55:31 +00:00
241381c7fa Fix copy tasks to run every time 2014-12-12 13:17:48 +00:00
7a7ba093db Added CHANGELOG 2014-12-12 06:03:41 +00:00
f0e6744760 Prevent NPE 2014-12-10 12:03:34 +00:00
99002c1c5c Updated translations 2014-12-10 11:51:41 +00:00
605a6c1cf4 Bumped support-v4 dependency in client library to 21.0.2 2014-12-09 21:43:26 +00:00
954a9cc46b Bumped gradle plugin to 1.0.0 2014-12-09 11:56:19 +00:00
1ef838b966 Fixed NPE 2014-12-04 06:56:52 +00:00
c672ca05f5 0.9.17
i2p.i2p tag: i2p-0.9.17
2014-12-02 00:40:17 +00:00
c493e73889 Client library 0.4
i2p.i2p tag: i2p-0.9.17
2014-12-01 08:40:48 +00:00
2b7c280f5b Tunnel details 2014-12-01 08:39:25 +00:00
23eab8a90a FloatingActionButton for I2PTunnel and addressbook 2014-12-01 03:57:21 +00:00
c59103eb76 Comments 2014-12-01 03:14:55 +00:00
f00a35ee09 Move versionCode and versionName definitions to build.gradle 2014-11-30 21:24:47 +00:00
af93725c01 Binding for Services 2014-11-30 11:35:15 +00:00
3953301c57 Fix intent generation 2014-11-30 10:55:34 +00:00
2dab9d5d4f Upgraded support libraries to 21.0.2 2014-11-27 11:42:48 +00:00
b77666fa26 New client library translations for ro 2014-11-25 11:44:46 +00:00
eca931c1b5 Updated translations 2014-11-25 11:44:22 +00:00
86ae77701f 0.9.16-rc1
i2p.i2p: cb66382d9716f7d9cd9441830face910705253e0
2014-11-25 04:48:48 +00:00
c1ee0c4d9e Updated Gradle Witness
Source: https://github.com/WhisperSystems/gradle-witness
Git commit: 10f1269c0aafdc1d478efc005ed48f3a47d44278
2014-11-25 03:51:52 +00:00
e632b35862 Bind to IRouterState using explicit intents (required for API 21 and up) 2014-11-16 18:49:11 +00:00
20d2dcd891 Updated translations 2014-11-16 03:06:29 +00:00
61d5ba5a7c Toolbar back navigation 2014-11-10 11:59:54 +00:00
339f688b7c I2PTunnel help 2014-11-10 11:55:33 +00:00
fed11e703a Don't start new activity if we are already there 2014-11-10 11:33:36 +00:00
438df8142a Settings back navigation 2014-11-10 11:27:11 +00:00
7b3730be24 Updated i2ptunnel.config, dropped Telecomix and Nameless 2014-11-10 10:43:11 +00:00
d6c20bafb3 Material design: one-line text lists 2014-11-10 10:42:05 +00:00
b8998db3a3 Revert to general Intent for selected addressbook entries 2014-11-10 05:39:48 +00:00
9ab1c84878 Material design: settings 2014-11-10 03:41:49 +00:00
2f3335d361 Material design: list items 2014-11-10 02:30:00 +00:00
0e8d900ed4 I2PTunnel secondary action to open linkable tunnels 2014-11-10 01:56:35 +00:00
2c7ce0b7c6 News fragment margins 2014-11-09 12:24:59 +00:00
3e2bdacf89 Material design: I2PTunnel list 2014-11-09 12:13:32 +00:00
64c32076a1 Show correct browser config activity 2014-11-08 07:50:50 +00:00
4d75ce7de1 Missing files 2014-11-08 07:50:30 +00:00
269ae2f569 Help details back nav in toolbar 2014-11-08 06:46:04 +00:00
a42a4b2c99 Reworked help navigation 2014-11-08 03:41:14 +00:00
96f5c2b488 Change property names to match original script (better interop with other repos) 2014-11-08 03:40:48 +00:00
09ab9779aa Addressbook doesn't use two columns yet 2014-11-05 00:50:55 +00:00
97ed0a3a8f NPE fix 2014-11-04 12:18:56 +00:00
ec6d225dc6 Icons for uninstalled supported browsers 2014-11-04 12:07:41 +00:00
800a332691 Swap Orfox with Firefox in supported browsers (no official Orfox release yet) 2014-11-04 12:02:02 +00:00
45eae17561 Simplify browser list UX 2014-11-04 11:54:22 +00:00
092365cab2 Help twopane view 2014-11-04 03:06:03 +00:00
c98c2f101d Updated TODO 2014-11-04 02:46:20 +00:00
8e86634a41 Recommended browsers, Orfox config, link to app store from browser list 2014-11-04 02:43:34 +00:00
7424e5b707 Browser configuration guides for embedded, Orweb, unknown and unsupported 2014-11-04 01:03:59 +00:00
5175c937a9 Help: List browsers, detect ones we know can (not) be configured for I2P 2014-11-03 12:26:58 +00:00
2692a567ab BetterAsyncTaskLoader from Bote 2014-11-03 12:23:24 +00:00
2de971fb52 New translations 2014-11-03 11:46:17 +00:00
403b2e8cd9 Material design: toolbar for help screen 2014-11-03 03:22:29 +00:00
22141e723a Fix back button when drawer indicator disabled 2014-11-02 23:20:13 +00:00
419758125e Updated translations 2014-11-02 12:42:44 +00:00
6db307c681 Client library 0.3
i2p.i2p tag: i2p-0.9.16
2014-11-02 01:46:33 +00:00
cdec6d2f98 Updated README 2014-11-02 01:36:11 +00:00
18a6dc9719 Use mavenLocal() repository instead of a local file repo 2014-11-02 01:35:55 +00:00
fb92d7858a Upgraded build tools to 21.0.2 2014-10-30 23:10:36 +00:00
b8e005bdd5 Copy tasks can only have one destination directory 2014-10-30 23:10:00 +00:00
6568489b27 Upgraded gradle wrapper and build tools 2014-10-30 12:15:49 +00:00
3ff3e14fc2 New Gradle Witness build
Includes a fix for Gradle 2.+ that is not yet pulled in:
https://github.com/WhisperSystems/gradle-witness/pull/3
2014-10-30 12:15:04 +00:00
092591f4ec Material theme: toolbar
Navigation tabs have all been replaced with spinners, because there are no nice
tab libraries for API 9+.
2014-10-29 11:47:53 +00:00
895a3a1dcc Fix deprecation 2014-10-27 21:37:52 +00:00
dc4ce3f8c7 Updated translations 2014-10-27 07:31:36 +00:00
7e3b9d5065 Updated translations 2014-10-22 21:15:55 +00:00
e58ffc9fd4 Material theme: nav drawer 2014-10-19 06:48:33 +00:00
0d029988c3 Material theme color definitions 2014-10-19 06:05:08 +00:00
6f01989a4b Nav drawer icon changed 2014-10-19 06:04:07 +00:00
19aeaec406 New material theme menu icons 2014-10-19 06:03:14 +00:00
b4d1241a9e Implement material styles properly with appcompat 2014-10-18 21:08:33 +00:00
fef4aa0e86 Initial migration to API 21 2014-10-18 06:11:39 +00:00
766266b147 Updated translations 2014-10-18 02:36:53 +00:00
93410c07bb Updated ignores 2014-10-17 08:02:24 +00:00
dc27a782b0 Fixed NPE in client library with State unparceling
This only fixes the symptom, but the crash log on Google Play doesn't shed light
on what the problem is.
2014-10-17 06:54:26 +00:00
zzz
b633df73c0 Update imports for move of router data structures 2014-10-16 23:10:32 +00:00
41d1200df7 0.9.15.1 2014-10-16 10:47:27 +00:00
c9a62fba9a Drop interface that isn't in source yet 2014-10-16 10:25:12 +00:00
c9598fa831 Update default subscriptions 2014-10-16 10:16:06 +00:00
9965c31b2d Pass dir and file to Util.mergeResourceToFile() in the correct order 2014-10-16 10:15:13 +00:00
43de6425b2 New translations for client library 2014-10-16 10:13:56 +00:00
214 changed files with 3463 additions and 1134 deletions

View File

@ -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
View 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

View File

@ -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()
}
```

26
TODO
View File

@ -1,16 +1,34 @@
# 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
- Fill out help pages
- Proper browser configuration page
- Rewrite release notes to be release-specific
- Fix release notes UI, either make back button use clear or add buttons
- NetDB tablet view fixes
@ -32,10 +50,6 @@
- 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
# Long-term

View File

@ -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 4745225
versionName '0.9.17.1'
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:20.0.0'
compile 'com.android.support:appcompat-v7:20.0.0'
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:81f2b1c2c94efd5a4ec7fcd97b6cdcd00e87a933905c5c86103c7319eb024572',
'com.android.support:appcompat-v7:736f576ab0b68d27bdf18b1e7931566e6d8254b73965175313e87f8866b91547',
'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')

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">I2P DEBUG</string>
</resources>

View 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>

View 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>

View File

@ -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="4745221"
android:versionName="0.9.15">
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/I2PAndroid">
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"
@ -59,6 +59,14 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.help.BrowserConfigActivity"
android:label="Browser Configuration"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".LicenseActivity"
android:label="I2P License Information"
@ -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

View File

@ -0,0 +1,82 @@
package net.i2p.android.help;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
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(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;
}
}

View 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;
}
}

View File

@ -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();
}
}
}

View 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<Browser>();
Map<String, String> recommendedMap = new HashMap<String, String>();
for (int i = 0; i < recommended.size(); i++) {
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
}
Map<String, String> supportedMap = new HashMap<String, String>();
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();
}
}

View File

@ -2,28 +2,17 @@ package net.i2p.android.help;
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.ActionBarActivity;
import android.view.LayoutInflater;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ScrollView;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
import org.sufficientlysecure.htmltextview.HtmlTextView;
public class HelpActivity extends ActionBarActivity implements
BrowserConfigFragment.OnBrowserSelectedListener {
HelpListFragment.OnEntrySelectedListener {
public static final String CATEGORY = "help_category";
public static final int CAT_MAIN = 0;
public static final int CAT_CONFIGURE_BROWSER = 1;
@ -41,30 +30,10 @@ public class HelpActivity extends ActionBarActivity implements
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
SpinnerAdapter spinnerAdapter = ArrayAdapter.createFromResource(this,
R.array.help_categories, android.R.layout.simple_spinner_dropdown_item);
ActionBar.OnNavigationListener navigationListener = new ActionBar.OnNavigationListener() {
@Override
public boolean onNavigationItemSelected(int i, long l) {
showCategory(i);
return true;
}
};
actionBar.setListNavigationCallbacks(spinnerAdapter, navigationListener);
if (savedInstanceState == null) {
int category = getIntent().getIntExtra(CATEGORY, CAT_MAIN);
// TODO remove when addressbook and I2PTunnel help added
if (category > CAT_CONFIGURE_BROWSER)
category = CAT_MAIN;
actionBar.setSelectedNavigationItem(category);
showCategory(category);
}
// 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
@ -73,59 +42,16 @@ public class HelpActivity extends ActionBarActivity implements
// activity should be in two-pane mode.
mTwoPane = true;
}
}
private void showCategory(int category) {
Fragment f;
switch (category) {
case CAT_CONFIGURE_BROWSER:
//f = new BrowserConfigFragment();
f = HelpHtmlFragment.newInstance(R.raw.help_configure_browser);
break;
case CAT_ADDRESSBOOK:
f = HelpHtmlFragment.newInstance(R.raw.help_addressbook);
break;
case CAT_I2PTUNNEL:
f = HelpHtmlFragment.newInstance(R.raw.help_i2ptunnel);
break;
case CAT_MAIN:
default:
f = HelpHtmlFragment.newInstance(R.raw.help_main);
break;
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, new HelpListFragment())
.commit();
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.main_fragment, f);
if (mTwoPane)
ft.remove(getSupportFragmentManager().findFragmentById(R.id.detail_fragment));
ft.commit();
}
public static 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;
}
int category = getIntent().getIntExtra(CATEGORY, -1);
if (category >= 0)
showCategory(category);
}
@Override
@ -137,28 +63,63 @@ public class HelpActivity extends ActionBarActivity implements
@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);
case android.R.id.home:
onBackPressed();
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);
}
}
// BrowserConfigFragment.OnBrowserSelectedListener
// HelpListFragment.OnEntrySelectedListener
@Override
public void onBrowserSelected(String browserPackage) {
// TODO
public void onEntrySelected(int entry) {
if (entry == CAT_CONFIGURE_BROWSER) {
Intent i = new Intent(this, BrowserConfigActivity.class);
startActivity(i);
} else
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();
}
}
}

View 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;
}
}

View 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);
}
}

View File

@ -96,11 +96,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());

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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();
}
}
}

View File

@ -1,27 +1,32 @@
package net.i2p.android.i2ptunnel;
import java.util.List;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.i2ptunnel.util.TunnelConfig;
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,26 +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;
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);
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)
@ -251,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);

View File

@ -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)) {
@ -119,16 +127,12 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
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));
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

View File

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

View File

@ -233,7 +233,7 @@ class InitActivities {
* @param overrides local overrides or null
*/
private void mergeResourceToFile(int resID, String f, Properties overrides) {
Util.mergeResourceToFile(ctx, f, myDir, resID, overrides);
Util.mergeResourceToFile(ctx, myDir, f, resID, overrides, null);
}
/**

View File

@ -1,23 +1,13 @@
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;
@ -31,8 +21,9 @@ 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 {
@ -144,7 +135,6 @@ public class MainActivity extends I2PActivityBase implements
case R.id.menu_help:
Intent hi = new Intent(MainActivity.this, HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_MAIN);
startActivity(hi);
return true;
@ -245,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;
@ -270,7 +271,7 @@ public class MainActivity extends I2PActivityBase implements
super.handleMessage(msg);
}
}
};
}
private boolean canStart() {
RouterService svc = _routerService;
@ -300,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();
}
}

View File

@ -9,10 +9,15 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.util.IntEditTextPreference;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.stat.FrequencyStat;
@ -24,6 +29,7 @@ import net.i2p.util.LogManager;
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 {
@ -33,6 +39,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) {
@ -50,11 +58,14 @@ public class SettingsActivity extends PreferenceActivity {
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
} else if (ACTION_PREFS_ADVANCED.equals(action)) {
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(this, getPreferenceScreen(), Util.getRouterContext());
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.settings_headers_legacy);
}
mToolbar.setTitle(getTitle());
}
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
@ -145,6 +156,24 @@ public class SettingsActivity extends PreferenceActivity {
}
}
protected static void setupAdvancedSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
if (ctx != null) {
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
IntEditTextPreference udpPort = (IntEditTextPreference) ps.findPreference(udpPortKey);
IntEditTextPreference ntcpPort = (IntEditTextPreference) ps.findPreference(ntcpPortKey);
String udpCurrentPort = ctx.getProperty(udpPortKey, "0");
udpPort.setText(udpCurrentPort);
String ntcpCurrentPort = ctx.getProperty(ntcpPortKey);
if (ntcpCurrentPort != null && ntcpCurrentPort.length() > 0)
ntcpPort.setText(ntcpCurrentPort);
else
ntcpPort.setText(udpCurrentPort);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
@ -155,24 +184,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);
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props);
Set toRemove = propsToRemove.keySet();
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props, toRemove);
// Apply new config if we are running.
RouterContext rCtx = Util.getRouterContext();
if (rCtx != null) {
rCtx.router().saveConfig(props, null);
rCtx.router().saveConfig(props, toRemove);
// Merge in new log settings
saveLoggingChanges(rCtx, logSettings);
} else {
// Merge in new config settings, write the file.
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.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);
@ -221,6 +274,7 @@ public class SettingsActivity extends PreferenceActivity {
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
} else if ("advanced".equals(settings)) {
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
}
}
}

View File

@ -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);

View File

@ -1,38 +1,43 @@
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>();

View File

@ -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);
}
}

View File

@ -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.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,26 +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;
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);
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);
}
}
@ -224,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);
}
}
}
}

View File

@ -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) {

View File

@ -11,6 +11,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
@ -26,8 +27,7 @@ public class ConfigureBrowserDialog extends DialogFragment {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_CONFIGURE_BROWSER);
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
}
})

View File

@ -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

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -8,7 +8,7 @@ import java.util.TreeSet;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

View File

@ -7,8 +7,8 @@ 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.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.ObjectCounter;
import android.content.Context;
@ -37,7 +37,7 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
ObjectCounter<String> countries = new ObjectCounter<String>();
ObjectCounter<String> transports = new ObjectCounter<String>();
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());

View File

@ -14,6 +14,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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;
@ -45,7 +46,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);

View File

@ -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);
}
}
}

View File

@ -1,15 +1,5 @@
package net.i2p.android.router.stats;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
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;
@ -17,13 +7,28 @@ 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.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.SpinnerAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
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 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
@ -40,8 +45,8 @@ public class RateGraphActivity extends I2PActivityBase {
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,33 +55,28 @@ 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<String>(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() {
@Override
@ -130,6 +130,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 +150,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> {

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -8,10 +8,12 @@ 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;
@ -19,13 +21,14 @@ 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 {
public abstract class Util implements I2PConstants {
public static String getOurVersion(Context ctx) {
PackageManager pm = ctx.getPackageManager();
String us = ctx.getPackageName();
@ -123,29 +126,33 @@ 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>();
// Copy prefs
Properties routerProps = new OrderedProperties();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
// List to store stats for graphing
List<String> statSummaries = new ArrayList<String>();
// 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.")) {
if ( x.startsWith("stat.summaries.")) {
String stat = x.substring("stat.summaries.".length());
String checked = all.get(x).toString();
if (checked.equals("true")) {
@ -160,7 +167,7 @@ public abstract class Util {
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);
}
@ -176,7 +183,22 @@ 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"));
System.out.println("UDP port: " + udpPort);
if (udpPort <= 0)
routerProps.remove(UDPTransport.PROP_INTERNAL_PORT);
int ntcpPort = Integer.parseInt(routerProps.getProperty(PROP_I2NP_NTCP_PORT, "-1"));
System.out.println("NTCP port: " + ntcpPort);
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;
@ -192,8 +214,12 @@ public abstract class Util {
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);
}
@ -209,9 +235,10 @@ public abstract class Util {
* </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) {
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)) {
@ -225,17 +252,17 @@ public abstract class Util {
for (Map.Entry<String, Boolean> option : booleanOptionsRequiringRestart.entrySet()) {
String propName = option.getKey();
boolean defaultValue = option.getValue();
restartRequired |= (
Boolean.parseBoolean(props.getProperty(propName, Boolean.toString(defaultValue))) !=
(defaultValue ? rCtx.getBooleanPropertyDefaultTrue(propName) : rCtx.getBooleanProperty(propName))
);
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();
restartRequired |= props.getProperty(propName, defaultValue).equals(
rCtx.getProperty(propName, defaultValue));
String currentValue = rCtx.getProperty(propName, defaultValue);
String newValue = props.getProperty(propName, defaultValue);
restartRequired |= !currentValue.equals(newValue);
}
}
}
@ -256,7 +283,7 @@ public abstract class Util {
* @param props properties to set
*/
public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) {
mergeResourceToFile(ctx, dir, file, 0, props);
mergeResourceToFile(ctx, dir, file, 0, props, null);
}
/**
@ -267,8 +294,10 @@ public abstract class Util {
* @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) {
public static void mergeResourceToFile(Context ctx, String dir, String file, int resID,
Properties userProps, Collection<String> toRemove) {
InputStream fin = null;
InputStream in = null;
@ -297,6 +326,11 @@ public abstract class Util {
// 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);

View File

@ -194,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 {

View File

@ -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;

View File

@ -16,7 +16,11 @@ import java.util.Queue;
* @author zzz
*/
class LogWriter implements Runnable {
/** every 10 seconds? why? Just have the gui force a reread after a change?? */
private final static long CONFIG_READ_ITERVAL = 10 * 1000;
final static long FLUSH_INTERVAL = 29 * 1000;
private final static long MIN_FLUSH_INTERVAL = 2*1000;
private final static long MAX_FLUSH_INTERVAL = 5*60*1000;
private long _lastReadConfig = 0;
private long _numBytesInCurrentFile = 0;
private OutputStream _currentOut; // = System.out
@ -26,6 +30,8 @@ class LogWriter implements Runnable {
private LogManager _manager;
private boolean _write;
// ms
private volatile long _flushInterval = FLUSH_INTERVAL;
private LogWriter() { // nop
}
@ -38,6 +44,14 @@ class LogWriter implements Runnable {
_write = false;
}
/**
* @param interval in ms
* @since 0.9.18
*/
public void setFlushInterval(long interval) {
_flushInterval = Math.min(MAX_FLUSH_INTERVAL, Math.max(MIN_FLUSH_INTERVAL, interval));
}
public void run() {
_write = true;
try {
@ -77,7 +91,7 @@ class LogWriter implements Runnable {
if (shouldWait) {
try {
synchronized (this) {
this.wait(10*1000);
this.wait(_flushInterval);
}
} catch (InterruptedException ie) { // nop
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

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