Compare commits

...

167 Commits

Author SHA1 Message Date
17ab043a4b 0.9.18
i2p.i2p tag: i2p-0.9.18
2015-03-04 12:13:45 +00:00
32b2b0ce75 Updated TODO with Silent Store checklist (useful reference) 2015-03-04 12:12:44 +00:00
b77e2ebbe5 Updated translations 2015-03-04 01:11:48 +00:00
9eeab68cdb String translation fix 2015-03-04 01:11:44 +00:00
96257015a9 Language cleanups 2015-03-03 23:37:56 +00:00
d7f6e3688c UDP and NTCP ports, part 4 2015-03-03 22:50:26 +00:00
5ef434e29f Updated translations 2015-03-03 13:23:16 +00:00
852d695dac UDP and NTCP ports, part 3 2015-03-03 13:21:32 +00:00
96cb8ab410 Bugfix 2015-03-03 00:19:36 +00:00
cd158cca84 Updated translations 2015-03-02 22:57:37 +00:00
b71a0a27d3 Fixed help Activity back and up nav 2015-03-02 22:57:03 +00:00
64268c7af8 Activity title fixes 2015-03-02 21:45:13 +00:00
95749f032e I2PTunnel -> Tunnels / Hidden Services Manager; string tweaks 2015-03-02 21:29:34 +00:00
0ac1ae56b0 Dropped unnecessary part of tunnels guide 2015-03-02 21:17:43 +00:00
c52bc45910 Client library 0.5.1
i2p.i2p tag: i2p-0.9.18
2015-03-02 21:16:57 +00:00
064ebc6857 Moved missing class into client library 2015-03-02 11:03:02 +00:00
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
b98cdf3e0b 0.9.15 2014-10-16 04:32:52 +00:00
26c11ebaed Updated translations 2014-10-16 04:30:49 +00:00
c9c3de1d04 Updated Androidplot to 0.6.1 2014-10-16 04:30:25 +00:00
9b29b56982 Upgrade support libraries to v20.0.0 2014-10-15 22:19:58 +00:00
8956fd7ce0 Updated translation strings 2014-10-15 21:52:46 +00:00
f1f94ea3e0 New translations 2014-10-15 21:49:32 +00:00
94b433aead Padding fix 2014-10-15 21:31:27 +00:00
5623d54114 Basic help page with HTML content 2014-10-15 21:21:11 +00:00
9133a2f848 Prepare for material theme 2014-10-15 10:36:55 +00:00
70324c3ecc Missing class 2014-10-15 02:50:37 +00:00
5613d21324 Replace first start warning dialog with browser config dialog 2014-10-14 11:15:14 +00:00
9036bf3f6b Re-enable (blank) help activity 2014-10-14 11:14:28 +00:00
94991d2df3 New type and status styles for tunnel list 2014-10-07 06:22:09 +00:00
76f23946f0 Don't show dialog on new version until we have something new to tell them 2014-10-05 04:12:29 +00:00
862e856913 Fixed graphs settings page notice when router not running 2014-10-05 03:38:17 +00:00
cf3de34cb6 Removed "participating" option from settings UI
Temporarily hidden because of problems with changing defaults. We don't want to
cause network instability because we have thousands of new unstable routers
churning the network, and can't change their default settings.

In future, we should look at the user's network and settings, and try to have 
"smart" defaults based on a user-chosen profile, or based on net type - see
Connectivity.isConnectionFast().
2014-10-05 02:59:18 +00:00
81de79c725 Remove "enable I2CP" from settings UI
Android apps should use the client library to connect to I2P Android. The TCP    
interface would only be useful if using the Android device as a gateway or 
dedicated router, but it would need to listen globally, not locally. If someone
asks for that in future, we can add this back and improve the configuration.
2014-10-05 02:53:09 +00:00
228b6f1baa Comment out disabling UPnP until other changes are finished 2014-10-05 01:17:31 +00:00
8e395cfd4e Detect more config options requiring restart 2014-10-05 01:16:17 +00:00
6d6123df9b Inform user that changing uPnP setting will require a router restart 2014-10-03 01:09:32 +00:00
321a49156c Display news in TextView instead of WebView 2014-10-03 01:04:26 +00:00
e8a47e17fb Developer note in default router.config 2014-10-02 11:02:06 +00:00
9df27ea168 Move property file writing methods to Util 2014-10-02 04:06:17 +00:00
cb6b7c4f48 getRouterContext() helper function 2014-10-02 03:55:24 +00:00
ca623e6b18 New class for connectivity checks
Source: https://gist.github.com/str4d/22cac7a3f70bc227cdca
License: http://opensource.org/licenses/MIT
2014-10-02 02:02:26 +00:00
8b6e02136e Fix TextResourceDialog scrolling 2014-10-01 21:06:07 +00:00
6a0493a578 Updated release notes 2014-10-01 20:58:22 +00:00
bf2a437a82 Initial news space fixes 2014-10-01 20:47:02 +00:00
ac949e3f5e Don't use wide view for news webview 2014-10-01 10:50:09 +00:00
7483251393 Copy log entries to clipboard, tweak copy text 2014-10-01 10:31:28 +00:00
d690b7d861 Copy logs to clipboard 2014-10-01 10:23:36 +00:00
829695d690 Move licenses menu option to about dialog button 2014-10-01 03:31:18 +00:00
261 changed files with 5555 additions and 1753 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()
}
```

80
TODO
View File

@ -1,18 +1,35 @@
# Fixes
- Better addressbook column widths
- Better twopane column widths
<zzz> on the i2ptunnel and addressbook pages on the tablet, the columns are too skinny, they aren't as wide as the tab
<zzz> only a few addressbook entries wrap but on i2ptunnel everything is wrapped and most of the screen is empty
- Create tunnel wizard
<zzz> in the tunnel create wizard:
<zzz> 'this could be the full base 64 destination key, or an i2p url from your address book"
<zzz> 'host name' better than 'URL'. Technically speaking, a host name is not a URL
<zzz> hmm would be nice if they could be shared-client or have an option
<zzz> was setting up email tunnels
- I2PTunnel details
<zzz> on the i2ptunnel details, it lists both a 'target' and 'access point' for clients, with the same info
<zzz> generally we use 'target' for servers and 'access point' for clients, never both
- Browser
<zzzccc> Bug report: i2p browser treats 302 as an error
<zzzccc> Bug 2: rotate screen in i2p browser seems to go back one page
# Short-term
- Graceful shutdown option
<zzzccc> Request: graceful shutdown
- Disable uPnP when on cell networks
<zzz> spewing UPnP out into cell networks is a waste of time at best and a security risk at worst, but you really want it for wifi
- I2PTunnel
- Show all messages somewhere
- Improve detail page, expose advanced settings
- Add edit page
- Progress feedback for addressbook subscriptions reload
- Display release notes directly on new router version
- Text content
- Move help content from release notes to help page
- Rewrite release notes to be release-specific
- Fill out help page
- Fill out help pages
- Rewrite release notes to be release-specific
- Fix release notes UI, either make back button use clear or add buttons
- NetDB tablet view fixes
- Refresh detail fragment when changing tab
@ -20,20 +37,63 @@
- Create nav history when viewing RI from LS
- Include GeoIP db for country info
- Maybe change router-off mechanic for various pages? Enable as they become available?
- Add "copy (error) log" option
# Medium-term
- Network profiles
- User selects profile in settings
- Change network participation etc. based on profile
- Also look at connection type: Connectivity.isConnectionFast()
- Expose log level overrides
- Improve graphs
- Show time on bottom axis
- Show fixed x range, not only available data
- Think about pan/zoom
- How to persist data across restarts?
- I2PTunnel
- Show all messages somewhere
- Improve detail page, expose advanced settings
- Add edit page
# Silent Store approval checks to confirm/implement
- Known Vulnerabilities
- Apps will be tested to ensure that they are not susceptible to known
publicly disclosed vulnerabilities. For example:
- Heartbleed
- Poodle
- MasterKey
- Common Path Traversal attacks
- Common SQL Injection attacks
- Network Security Protocols
- All Apps that require transmission of data from the App to a system that
does not exist on the device must use, at a minimum, TLS1.1 standards.
However, Blackphone would prefer the usage of TLS1.2.
- Apps must not use algorithms for cryptographic purposes that are considered
obsolete or outdated i.e. MD5, SHA1, RC4, DES, or any encryption algorithm
that is weaker than AES128.
- Transport Layer Protection
- All network communication should be encrypted
- Not vulnerable to SSl Strip
- Data Leakage
- No storage of sensitive data outside of application sandbox
- Files should not be created with MODE_WORLD_READABLE or MODE_WORLD_WRITABLE
- Copy & Paste will be evaluated on a case by case basis
- App logs should not contain sensitive information
- Authentication and Authorization
- Validate that authentication credentials are not stored on the device
- Must use an approved password-based key derivation function ie. PBKDF2, scrypt
- Data-at-rest Encryption
- Must use at a minimum AES128 with modes CCM or GCM
- Should not store the encryption key on the file system
- Permission Checks
- The App must function with all permissions disabled
- Apps must not hard crash if a permission is disabled
- Apps should ask users to enable permissions that are disabled if needed to
function properly and explain why the permission is necessary
- Privacy Policy
- Apps must have a privacy policy that details how customer data is used,
stored, shared, etc...
- Apps must be configured with the customer opted out by default
- App logs should not contain PII
- Error Handling
- Apps should follow best-practices for error handling and logging
# Long-term

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 4745226
versionName '0.9.18'
minSdkVersion 9
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
}
@ -14,9 +16,17 @@ android {
buildTypes {
release {
signingConfig signingConfigs.release
runProguard false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
lintOptions {
abortOnError false
@ -37,15 +47,19 @@ android {
dependencies {
compile project(':routerjars')
compile project(':client')
compile 'com.android.support:support-v4:19.1.0'
compile 'com.android.support:appcompat-v7:19.1.0'
compile files('libs/androidplot-core-0.6.0.jar')
compile 'com.android.support:support-v4:21.0.3'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
compile 'net.i2p.android.ext:floatingactionbutton:1.8.0'
compile files('libs/androidplot-core-0.6.1.jar')
}
dependencyVerification {
verify = [
'com.android.support:support-v4:3f40fa7b3a4ead01ce15dce9453b061646e7fe2e7c51cb75ca01ee1e77037f3f',
'com.android.support:appcompat-v7:9df7637c5219202ddbbbf0924c2d5a9e6d64379166795a89d8f75d1e3f3372df',
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
'net.i2p.android.ext:floatingactionbutton:a20d1f0cae15f8965b81486ba31245937968ae6ee5fa6e8a3ea21d7f6c6243ab',
]
}
@ -71,15 +85,17 @@ task certificatesZip(type: Zip) {
from files('' + i2pbase + '/installer/resources/certificates')
}
task copyI2PResources(type: Copy) {
into 'src/main'
into('res/drawable') {
// Force this to always run: Copy only detects source changes, not if missing in destination
outputs.upToDateWhen { false }
into 'src/main/res'
into('drawable') {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
}
into('res/raw') {
from(i2pbase + '/installer/resources/blocklist.txt') { rename {'blocklist_txt' } }
from(i2pbase + '/installer/resources/hosts.txt') { rename {'hosts_txt' } }
from('../LICENSE.txt') { rename {'license_app_txt' } }
from('../licenses/LICENSE-Apache2.0.txt') { rename {'license_apache20_txt' } }
into('raw') {
from(i2pbase + '/installer/resources/blocklist.txt') { rename { 'blocklist_txt' } }
from(i2pbase + '/installer/resources/hosts.txt') { rename { 'hosts_txt' } }
from('../LICENSE.txt') { rename { 'license_app_txt' } }
from('../licenses/LICENSE-Apache2.0.txt') { rename { 'license_apache20_txt' } }
from(i2pbase + '/licenses') {
include { elem ->
elem.name in [
@ -105,19 +121,28 @@ task copyI2PResources(type: Copy) {
}
from certificatesZip
}
// For peers WebView
into('assets/themes/console/images') {
}
// For peers WebView
task copyI2PAssets(type: Copy) {
// Force this to always run: Copy only detects source changes, not if missing in destination
outputs.upToDateWhen { false }
into 'src/main/assets/themes/console'
into('images') {
from file(i2pbase + '/installer/resources/themes/console/images/i2plogo.png')
from file(i2pbase + '/installer/resources/themes/console/images/inbound.png')
from file(i2pbase + '/installer/resources/themes/console/images/outbound.png')
}
into ('assets/themes/console/light') {
into('light') {
from file(i2pbase + '/installer/resources/themes/console/light/console.css')
}
into('assets/themes/console/light/images') {
into('light/images') {
from file(i2pbase + '/installer/resources/themes/console/light/images/header.png')
}
}
preBuild.dependsOn copyI2PResources
preBuild.dependsOn copyI2PAssets
task cleanI2PResources(type: Delete) {
delete file('src/main/res/drawable/i2plogo.png')
delete fileTree('src/main/res/raw') {
@ -126,12 +151,15 @@ task cleanI2PResources(type: Delete) {
include 'license_*'
include 'certificates_zip'
}
}
task cleanI2PAssets(type: Delete) {
delete fileTree('src/main/assets/themes/console/images')
delete file('src/main/assets/themes/console/light/console.css')
delete file('src/main/assets/themes/console/light/images/header.png')
}
preBuild.dependsOn copyI2PResources
clean.dependsOn cleanI2PResources
clean.dependsOn cleanI2PAssets
props = new Properties()
propFile = new File(project.rootDir, 'signing.properties')

Binary file not shown.

Binary file not shown.

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="4745219"
android:versionName="0.9.14.1-rc3">
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher_itoopie"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat">
android:theme="@style/Theme.I2P">
<service
android:name=".service.RouterService"
android:icon="@drawable/ic_launcher_itoopie"
@ -26,7 +21,12 @@
</service>
<provider
android:name=".provider.CacheProvider"
android:authorities="net.i2p.android" />
android:authorities="${applicationId}.provider" />
<receiver android:name=".receiver.OnBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
@ -45,15 +45,23 @@
<activity
android:name=".NewsActivity"
android:configChanges="orientation|keyboardHidden"
android:label="I2P News"
android:label="@string/label_news"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name=".HelpActivity"
android:label="Help"
android:name="net.i2p.android.help.HelpActivity"
android:label="@string/menu_help"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.MainActivity" />
</activity>
<activity
android:name="net.i2p.android.help.BrowserConfigActivity"
android:label="@string/label_browser_configuration"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -61,11 +69,11 @@
</activity>
<activity
android:name=".LicenseActivity"
android:label="I2P License Information"
android:parentActivityName=".HelpActivity">
android:label="@string/label_licenses"
android:parentActivityName="net.i2p.android.help.HelpActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.HelpActivity" />
android:value="net.i2p.android.help.HelpActivity" />
</activity>
<activity
android:name=".web.WebActivity"
@ -86,7 +94,7 @@
</activity>
<activity
android:name=".SettingsActivity"
android:label="I2P Settings"
android:label="@string/menu_settings"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@ -94,7 +102,7 @@
</activity>
<activity
android:name=".addressbook.AddressbookSettingsActivity"
android:label="I2P Addressbook Settings"
android:label="Addressbook Settings"
android:launchMode="singleTop"
android:parentActivityName=".addressbook.AddressbookActivity">
<meta-data
@ -103,7 +111,7 @@
</activity>
<activity
android:name=".addressbook.AddressbookActivity"
android:label="Addressbook"
android:label="@string/label_addressbook"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
@ -127,7 +135,7 @@
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelListActivity"
android:label="I2PTunnel"
android:label="@string/label_i2ptunnel"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity">
<meta-data
@ -140,7 +148,7 @@
android:parentActivityName="net.i2p.android.i2ptunnel.TunnelListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="net.i2p.android.router.i2ptunnel.TunnelListActivity" />
android:value="net.i2p.android.i2ptunnel.TunnelListActivity" />
</activity>
<activity
android:name="net.i2p.android.i2ptunnel.TunnelWizardActivity"

View File

@ -0,0 +1,83 @@
package net.i2p.android.help;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
public class Browser implements Comparable<Browser> {
public final String packageName;
public final CharSequence label;
public final Drawable icon;
public final boolean isInstalled;
public final boolean isKnown;
public final boolean isSupported;
public final boolean isRecommended;
/**
* A browser that we don't know about.
*
* @param pm the PackageManager used to find the browser
* @param browser the browser
*/
public Browser(PackageManager pm, ResolveInfo browser) {
this(
browser.activityInfo.packageName,
browser.loadLabel(pm),
browser.loadIcon(pm),
true, false, false, false
);
}
/**
* A browser that we know about.
*
* @param pm the PackageManager used to find the browser
* @param browser the browser
* @param supported can this browser be used with I2P?
*/
public Browser(PackageManager pm, ResolveInfo browser, boolean supported, boolean recommended) {
this(
browser.activityInfo.packageName,
browser.loadLabel(pm),
browser.loadIcon(pm),
true, true, supported, recommended
);
}
public Browser(String pn, CharSequence l, Drawable ic, boolean i, boolean k, boolean s, boolean r) {
packageName = pn;
label = l;
icon = ic;
isInstalled = i;
isKnown = k;
isSupported = s;
isRecommended = r;
}
@Override
public int compareTo(@NonNull Browser browser) {
// Sort order: supported -> unknown -> unsupported
int a = getOrder(this);
int b = getOrder(browser);
if (a < b)
return -1;
else if (a > b)
return 1;
return label.toString().compareTo(browser.label.toString());
}
private static int getOrder(Browser browser) {
if (browser.isKnown) {
if (browser.isRecommended)
return 0;
else if (browser.isSupported)
return 1;
else
return 3;
} else
return 2;
}
}

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<>();
Map<String, String> recommendedMap = new HashMap<>();
for (int i = 0; i < recommended.size(); i++) {
recommendedMap.put(recommended.get(i), recommendedLabels.get(i));
}
Map<String, String> supportedMap = new HashMap<>();
for (int i = 0; i < supported.size(); i++) {
supportedMap.put(supported.get(i), supportedLabels.get(i));
}
// Find all installed browsers that listen for ".i2p"
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://stats.i2p"));
final PackageManager pm = getContext().getPackageManager();
List<ResolveInfo> installedBrowsers = pm.queryIntentActivities(intent, 0);
for (ResolveInfo browser : installedBrowsers) {
if (recommended.contains(browser.activityInfo.packageName)) {
browsers.add(new Browser(pm, browser, true, true));
recommendedMap.remove(browser.activityInfo.packageName);
} else if (supported.contains(browser.activityInfo.packageName) ||
browser.activityInfo.packageName.startsWith("net.i2p.android")) {
browsers.add(new Browser(pm, browser, true, false));
supportedMap.remove(browser.activityInfo.packageName);
} else if (unsupported.contains(browser.activityInfo.packageName))
browsers.add(new Browser(pm, browser, false, false));
else
browsers.add(new Browser(pm, browser));
}
// Now add the remaining recommended and supported browsers
for (Map.Entry<String, String> browser : recommendedMap.entrySet()) {
browsers.add(new Browser(browser.getKey(), browser.getValue(),
getDrawableForPackage(browser.getKey()),
false, true, true, true));
}
for (Map.Entry<String, String> browser : supportedMap.entrySet()) {
browsers.add(new Browser(browser.getKey(), browser.getValue(),
getDrawableForPackage(browser.getKey()),
false, true, true, false));
}
Collections.sort(browsers);
return browsers;
}
private Drawable getDrawableForPackage(String packageName) {
try {
String name = "icon_" + packageName.replace('.', '_');
Class res = R.drawable.class;
Field field = res.getField(name);
int drawable = field.getInt(null);
return getContext().getResources().getDrawable(drawable);
} catch (Exception e) {
return null;
}
}
@Override
protected void onStartMonitoring() {
}
@Override
protected void onStopMonitoring() {
}
@Override
protected void releaseResources(List<Browser> data) {
}
}
@Override
public void onLoadFinished(Loader<List<Browser>> listLoader, List<Browser> browsers) {
if (listLoader.getId() == BROWSER_LOADER_ID)
mAdapter.setBrowsers(browsers.toArray(new Browser[browsers.size()]));
}
@Override
public void onLoaderReset(Loader<List<Browser>> listLoader) {
if (listLoader.getId() == BROWSER_LOADER_ID)
mAdapter.clear();
}
}

View File

@ -0,0 +1,155 @@
package net.i2p.android.help;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
public class HelpActivity extends ActionBarActivity implements
HelpListFragment.OnEntrySelectedListener {
public static final String CATEGORY = "help_category";
public static final int CAT_MAIN = 0;
public static final int CAT_CONFIGURE_BROWSER = 1;
public static final int CAT_ADDRESSBOOK = 2;
public static final int CAT_I2PTUNNEL = 3;
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private int mCategory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
// Set the action bar
Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (findViewById(R.id.detail_fragment) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
}
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, new HelpListFragment())
.commit();
}
mCategory = getIntent().getIntExtra(CATEGORY, -1);
if (mCategory >= 0) {
showCategory(mCategory);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (mCategory >= 0) {
onBackPressed();
} else {
Intent upIntent = NavUtils.getParentActivityIntent(this);
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is NOT part of this app's task, so create a new task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
// Add all of this activity's parents to the back stack
.addNextIntentWithParentStack(upIntent)
// Navigate up to the closest parent
.startActivities();
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
}
return true;
case R.id.menu_help_licenses:
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
startActivity(lic);
return true;
case R.id.menu_help_release_notes:
TextResourceDialog dialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
dialog.setArguments(args);
dialog.show(getSupportFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (mCategory >= 0)
mCategory = -1;
}
// HelpListFragment.OnEntrySelectedListener
@Override
public void onEntrySelected(int entry) {
if (entry == CAT_CONFIGURE_BROWSER) {
Intent i = new Intent(this, BrowserConfigActivity.class);
startActivity(i);
} else {
mCategory = entry;
showCategory(entry);
}
}
private void showCategory(int category) {
int file;
switch (category) {
case CAT_ADDRESSBOOK:
file = R.raw.help_addressbook;
break;
case CAT_I2PTUNNEL:
file = R.raw.help_i2ptunnel;
break;
case CAT_MAIN:
default:
file = R.raw.help_main;
break;
}
HelpHtmlFragment f = HelpHtmlFragment.newInstance(file);
if (mTwoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.detail_fragment, f).commit();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f)
.addToBackStack("help" + category)
.commit();
}
}
}

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

@ -1,8 +1,9 @@
package net.i2p.android.i2ptunnel;
import android.os.Bundle;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
import android.os.Bundle;
public class TunnelDetailActivity extends I2PActivityBase implements
TunnelDetailFragment.OnTunnelDeletedListener {
@ -11,6 +12,8 @@ public class TunnelDetailActivity extends I2PActivityBase implements
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportActionBar().setDisplayShowTitleEnabled(false);
if (savedInstanceState == null) {
int tunnelId = getIntent().getIntExtra(TunnelDetailFragment.TUNNEL_ID, 0);
TunnelDetailFragment detailFrag = TunnelDetailFragment.newInstance(tunnelId);

View File

@ -1,15 +1,11 @@
package net.i2p.android.i2ptunnel;
import java.util.List;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
@ -22,6 +18,12 @@ import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import net.i2p.android.i2ptunnel.util.TunnelUtil;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.List;
public class TunnelDetailFragment extends Fragment {
public static final String TUNNEL_ID = "tunnel_id";
@ -96,11 +98,14 @@ public class TunnelDetailFragment extends Fragment {
TextView description = (TextView) v.findViewById(R.id.tunnel_description);
description.setText(mTunnel.getDescription());
TextView details = (TextView) v.findViewById(R.id.tunnel_details);
details.setText(mTunnel.getDetails());
TextView targetIfacePort = (TextView) v.findViewById(R.id.tunnel_target_interface_port);
targetIfacePort.setText(mTunnel.getIfacePort());
targetIfacePort.setText(mTunnel.getTunnelLink(false));
TextView accessIfacePort = (TextView) v.findViewById(R.id.tunnel_access_interface_port);
accessIfacePort.setText(mTunnel.getIfacePort());
accessIfacePort.setText(mTunnel.getTunnelLink(false));
CheckBox autoStart = (CheckBox) v.findViewById(R.id.tunnel_autostart);
autoStart.setChecked(mTunnel.startAutomatically());
@ -162,6 +167,7 @@ public class TunnelDetailFragment extends Fragment {
return true;
case R.id.action_delete_tunnel:
DialogFragment dg = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())

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,15 +1,15 @@
package net.i2p.android.i2ptunnel;
import java.util.ArrayList;
import java.util.List;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import android.content.Context;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import java.util.ArrayList;
import java.util.List;
public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
private TunnelControllerGroup mGroup;
private boolean mClientTunnels;
@ -26,7 +26,7 @@ public class TunnelEntryLoader extends AsyncTaskLoader<List<TunnelEntry>> {
@Override
public List<TunnelEntry> loadInBackground() {
List<TunnelEntry> ret = new ArrayList<TunnelEntry>();
List<TunnelEntry> ret = new ArrayList<>();
List<TunnelController> controllers = mGroup.getControllers();
for (int i = 0; i < controllers.size(); i++) {
TunnelEntry tunnel = new TunnelEntry(getContext(), controllers.get(i), i);

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.i2ptunnel.util.TunnelConfig;
import net.i2p.android.router.HelpActivity;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.RouterContext;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.Toast;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.i2ptunnel.util.TunnelConfig;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.android.router.R;
import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.router.RouterContext;
import java.util.List;
public class TunnelListFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
LoaderManager.LoaderCallbacks<List<TunnelEntry>> {
@ -50,6 +55,8 @@ public class TunnelListFragment extends ListFragment implements
private int mActivatedPosition = ListView.INVALID_POSITION;
private boolean mActivateOnItemClick = false;
private ImageButton mNewTunnel;
// Container Activity must implement this interface
public interface OnTunnelSelectedListener {
public void onTunnelSelected(int tunnelId);
@ -85,6 +92,27 @@ public class TunnelListFragment extends ListFragment implements
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Create the list fragment's content view by calling the super method
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
listContainer.addView(listFragmentView);
mNewTunnel = (ImageButton) v.findViewById(R.id.promoted_action);
mNewTunnel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
}
});
return v;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@ -162,7 +190,7 @@ public class TunnelListFragment extends ListFragment implements
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_i2ptunnel_list_actions, menu);
if (getRouterContext() == null) {
menu.findItem(R.id.action_add_tunnel).setVisible(false);
mNewTunnel.setVisibility(View.GONE);
menu.findItem(R.id.action_start_all_tunnels).setVisible(false);
menu.findItem(R.id.action_stop_all_tunnels).setVisible(false);
menu.findItem(R.id.action_restart_all_tunnels).setVisible(false);
@ -174,27 +202,22 @@ public class TunnelListFragment extends ListFragment implements
// Handle presses on the action bar items
List<String> msgs;
switch (item.getItemId()) {
case R.id.action_add_tunnel:
Intent wi = new Intent(getActivity(), TunnelWizardActivity.class);
startActivityForResult(wi, TUNNEL_WIZARD_REQUEST);
return true;
case R.id.action_start_all_tunnels:
msgs = mGroup.startAllControllers();
break;
case R.id.action_stop_all_tunnels:
msgs = mGroup.stopAllControllers();
break;
case R.id.action_restart_all_tunnels:
msgs = mGroup.restartAllControllers();
break;
// TODO: Enable when Help page finished
//case R.id.action_i2ptunnel_help:
// Intent hi = new Intent(getActivity(), HelpActivity.class);
// hi.putExtra(HelpActivity.REFERRER, "i2ptunnel");
// startActivity(hi);
// return true;
default:
return super.onOptionsItemSelected(item);
case R.id.action_start_all_tunnels:
msgs = mGroup.startAllControllers();
break;
case R.id.action_stop_all_tunnels:
msgs = mGroup.stopAllControllers();
break;
case R.id.action_restart_all_tunnels:
msgs = mGroup.restartAllControllers();
break;
case R.id.action_i2ptunnel_help:
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_I2PTUNNEL);
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
// TODO: Do something with the other messages
if (msgs.size() > 0)
@ -252,7 +275,7 @@ public class TunnelListFragment extends ListFragment implements
}
public void onLoadFinished(Loader<List<TunnelEntry>> loader,
List<TunnelEntry> data) {
List<TunnelEntry> data) {
if (loader.getId() == (mClientTunnels ?
CLIENT_LOADER_ID : SERVER_LOADER_ID)) {
mAdapter.setData(data);

View File

@ -1,16 +1,18 @@
package net.i2p.android.i2ptunnel;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.router.R;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
public class TunnelWizardActivity extends AbstractWizardActivity {
@Override
protected AbstractWizardModel onCreateModel() {
@ -20,6 +22,7 @@ public class TunnelWizardActivity extends AbstractWizardActivity {
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())

View File

@ -1,11 +1,5 @@
package net.i2p.android.i2ptunnel.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import android.content.Context;
import android.content.res.Resources;
@ -17,6 +11,12 @@ import net.i2p.i2ptunnel.TunnelControllerGroup;
import net.i2p.util.FileUtil;
import net.i2p.util.SecureFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public abstract class TunnelUtil {
public static TunnelController getController(TunnelControllerGroup tcg, int tunnel) {
if (tunnel < 0) return null;
@ -36,7 +36,7 @@ public abstract class TunnelUtil {
TunnelController cur = getController(tcg, tunnelId);
if (config == null) {
List<String> ret = new ArrayList<String>();
List<String> ret = new ArrayList<>();
ret.add("Invalid params");
return ret;
}
@ -101,7 +101,7 @@ public abstract class TunnelUtil {
List<String> msgs;
TunnelController cur = getController(tcg, tunnelId);
if (cur == null) {
msgs = new ArrayList<String>();
msgs = new ArrayList<>();
msgs.add("Invalid tunnel number");
return msgs;
}
@ -192,32 +192,34 @@ public abstract class TunnelUtil {
public static String getTypeName(String type, Context context) {
Resources res = context.getResources();
if ("client".equals(type))
return res.getString(R.string.i2ptunnel_type_client);
else if ("httpclient".equals(type))
return res.getString(R.string.i2ptunnel_type_httpclient);
else if ("ircclient".equals(type))
return res.getString(R.string.i2ptunnel_type_ircclient);
else if ("server".equals(type))
return res.getString(R.string.i2ptunnel_type_server);
else if ("httpserver".equals(type))
return res.getString(R.string.i2ptunnel_type_httpserver);
else if ("sockstunnel".equals(type))
return res.getString(R.string.i2ptunnel_type_sockstunnel);
else if ("socksirctunnel".equals(type))
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
else if ("connectclient".equals(type))
return res.getString(R.string.i2ptunnel_type_connectclient);
else if ("ircserver".equals(type))
return res.getString(R.string.i2ptunnel_type_ircserver);
else if ("streamrclient".equals(type))
return res.getString(R.string.i2ptunnel_type_streamrclient);
else if ("streamrserver".equals(type))
return res.getString(R.string.i2ptunnel_type_streamrserver);
else if ("httpbidirserver".equals(type))
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
else
return type;
switch (type) {
case "client":
return res.getString(R.string.i2ptunnel_type_client);
case "httpclient":
return res.getString(R.string.i2ptunnel_type_httpclient);
case "ircclient":
return res.getString(R.string.i2ptunnel_type_ircclient);
case "server":
return res.getString(R.string.i2ptunnel_type_server);
case "httpserver":
return res.getString(R.string.i2ptunnel_type_httpserver);
case "sockstunnel":
return res.getString(R.string.i2ptunnel_type_sockstunnel);
case "socksirctunnel":
return res.getString(R.string.i2ptunnel_type_socksirctunnel);
case "connectclient":
return res.getString(R.string.i2ptunnel_type_connectclient);
case "ircserver":
return res.getString(R.string.i2ptunnel_type_ircserver);
case "streamrclient":
return res.getString(R.string.i2ptunnel_type_streamrclient);
case "streamrserver":
return res.getString(R.string.i2ptunnel_type_streamrserver);
case "httpbidirserver":
return res.getString(R.string.i2ptunnel_type_httpbidirserver);
default:
return type;
}
}
public static boolean isClient(String type) {

View File

@ -1,52 +0,0 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Menu;
import android.view.MenuItem;
public class HelpActivity extends I2PActivityBase {
public static final String REFERRER = "help_referrer";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerToggle.setDrawerIndicatorEnabled(false);
/*if (savedInstanceState == null) {
HelpFragment f = new HelpFragment();
f.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment, f).commit();
}*/
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_help_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_help_licenses:
Intent lic = new Intent(HelpActivity.this, LicenseActivity.class);
startActivity(lic);
return true;
case R.id.menu_help_release_notes:
TextResourceDialog dialog = new TextResourceDialog();
Bundle args = new Bundle();
args.putString(TextResourceDialog.TEXT_DIALOG_TITLE,
getResources().getString(R.string.label_release_notes));
args.putInt(TextResourceDialog.TEXT_RESOURCE_ID, R.raw.releasenotes_txt);
dialog.setArguments(args);
dialog.show(getSupportFragmentManager(), "release_notes");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

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)) {
@ -118,17 +126,13 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
mDrawerList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Set the adapter for the list view
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, activityTitles));
mDrawerList.setAdapter(new ArrayAdapter<>(this,
R.layout.listitem_navdrawer, activityTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
// Enable ActionBar app icon to behave as action to toggle nav drawer
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
R.string.drawer_open, R.string.drawer_close) {
private boolean wasDragged = false;
/** Called when a drawer has settled in a completely closed state. */
@ -167,57 +171,63 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
private void selectItem(int pos) {
switch (pos) {
case 1:
Intent news = new Intent(I2PActivityBase.this, NewsActivity.class);
startActivity(news);
break;
case 2:
Intent ab = new Intent(I2PActivityBase.this, AddressbookActivity.class);
startActivity(ab);
break;
case 3:
Intent itb = new Intent(I2PActivityBase.this, TunnelListActivity.class);
startActivity(itb);
break;
case 4:
Intent log = new Intent(I2PActivityBase.this, LogActivity.class);
startActivity(log);
break;
case 5:
Intent wp = new Intent(I2PActivityBase.this, WebActivity.class);
wp.putExtra(WebFragment.HTML_RESOURCE_ID, R.raw.welcome_html);
startActivity(wp);
break;
case 6:
Intent active = new Intent(I2PActivityBase.this, RateGraphActivity.class);
startActivity(active);
break;
case 7:
Intent peers = new Intent(I2PActivityBase.this, PeersActivity.class);
startActivity(peers);
break;
case 8:
Intent netdb = new Intent(I2PActivityBase.this, NetDbActivity.class);
startActivity(netdb);
break;
default:
Intent main = new Intent(I2PActivityBase.this, MainActivity.class);
startActivity(main);
break;
case 1:
if (!(this instanceof NewsActivity)) {
Intent news = new Intent(I2PActivityBase.this, NewsActivity.class);
startActivity(news);
}
break;
case 2:
Intent ab = new Intent(I2PActivityBase.this, AddressbookActivity.class);
startActivity(ab);
break;
case 3:
Intent itb = new Intent(I2PActivityBase.this, TunnelListActivity.class);
startActivity(itb);
break;
case 4:
if (!(this instanceof LogActivity)) {
Intent log = new Intent(I2PActivityBase.this, LogActivity.class);
startActivity(log);
}
break;
case 5:
Intent wp = new Intent(I2PActivityBase.this, WebActivity.class);
wp.putExtra(WebFragment.HTML_RESOURCE_ID, R.raw.welcome_html);
startActivity(wp);
break;
case 6:
if (!(this instanceof RateGraphActivity)) {
Intent active = new Intent(I2PActivityBase.this, RateGraphActivity.class);
startActivity(active);
}
break;
case 7:
Intent peers = new Intent(I2PActivityBase.this, PeersActivity.class);
startActivity(peers);
break;
case 8:
if (!(this instanceof NetDbActivity)) {
Intent netdb = new Intent(I2PActivityBase.this, NetDbActivity.class);
startActivity(netdb);
}
break;
default:
Intent main = new Intent(I2PActivityBase.this, MainActivity.class);
startActivity(main);
break;
}
mDrawerLayout.closeDrawer(mDrawerList);
}
@Override
public void onRestart()
{
public void onRestart() {
Util.d(this + " onRestart called");
super.onRestart();
}
@Override
public void onStart()
{
public void onStart() {
Util.d(this + " onStart called");
super.onStart();
if (_sharedPrefs.getBoolean(PREF_AUTO_START, DEFAULT_AUTO_START))
@ -226,24 +236,32 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
bindRouter(false);
}
/** @param def default */
/**
* @param def default
*/
public boolean getPref(String pref, boolean def) {
return _sharedPrefs.getBoolean(pref, def);
}
/** @param def default */
/**
* @param def default
*/
public String getPref(String pref, String def) {
return _sharedPrefs.getString(pref, def);
}
/** @return success */
/**
* @return success
*/
public boolean setPref(String pref, boolean val) {
SharedPreferences.Editor edit = _sharedPrefs.edit();
edit.putBoolean(pref, val);
return edit.commit();
}
/** @return success */
/**
* @return success
*/
public boolean setPref(String pref, String val) {
SharedPreferences.Editor edit = _sharedPrefs.edit();
edit.putString(pref, val);
@ -251,37 +269,32 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
}
@Override
public void onResume()
{
public void onResume() {
Util.d(this + " onResume called");
super.onResume();
}
@Override
public void onPause()
{
public void onPause() {
Util.d(this + " onPause called");
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState)
{
public void onSaveInstanceState(Bundle outState) {
Util.d(this + " onSaveInstanceState called");
super.onSaveInstanceState(outState);
}
@Override
public void onStop()
{
public void onStop() {
Util.d(this + " onStop called");
unbindRouter();
super.onStop();
}
@Override
public void onDestroy()
{
public void onDestroy() {
Util.d(this + " onDestroy called");
super.onDestroy();
}
@ -301,6 +314,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
/**
* Override in subclass with e.g.
* menu.findItem(R.id.action_add_to_addressbook).setVisible(!drawerOpen);
*
* @param drawerOpen true if the drawer is open
*/
protected void onDrawerChange(boolean drawerOpen) {
@ -310,8 +324,11 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
public boolean onOptionsItemSelected(MenuItem item) {
// The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this.
if(mDrawerToggle.onOptionsItemSelected(item)) {
if (mDrawerToggle.onOptionsItemSelected(item))
return true;
else if (item.getItemId() == android.R.id.home) {
// This happens when mDrawerToggle.setDrawerIndicatorEnabled(false)
onBackPressed();
}
// Handle action buttons and overflow
@ -361,7 +378,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
////// Service stuff
/**
* Start the service and bind to it
* Start the service and bind to it
*/
protected boolean startRouter() {
Intent intent = new Intent();
@ -378,7 +395,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
}
/**
* Bind only
* Bind only
*/
protected boolean bindRouter(boolean autoCreate) {
Intent intent = new Intent(RouterBinder.class.getName());
@ -393,7 +410,7 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
protected void unbindRouter() {
Util.d(this + " unbindRouter called with _isBound:" + _isBound + " _connection:" + _connection + " _triedBind:" + _triedBind);
if (_triedBind && _connection != null)
unbindService(_connection);
unbindService(_connection);
_triedBind = false;
_connection = null;
@ -424,7 +441,9 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
}
}
/** callback from ServiceConnection, override as necessary */
/**
* callback from ServiceConnection, override as necessary
*/
protected void onRouterBind(RouterService svc) {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (f instanceof I2PFragmentBase)
@ -441,8 +460,11 @@ public abstract class I2PActivityBase extends ActionBarActivity implements
}
}
/** callback from ServiceConnection, override as necessary */
protected void onRouterUnbind() {}
/**
* callback from ServiceConnection, override as necessary
*/
protected void onRouterUnbind() {
}
// I2PFragmentBase.RouterContextProvider

View File

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

View File

@ -4,9 +4,12 @@ import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -15,12 +18,6 @@ import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
import net.i2p.util.OrderedProperties;
// Wouldn't this be better as a private class in MainActivity?
class InitActivities {
@ -36,8 +33,7 @@ class InitActivities {
public InitActivities(Context c) {
ctx = c;
// This needs to be changed so that we can have an alternative place
myDir = c.getFilesDir().getAbsolutePath();
myDir = Util.getFileDir(c);
_ourVersion = Util.getOurVersion(c);
}
@ -128,8 +124,7 @@ class InitActivities {
File certificates = new File(myDir, "certificates");
File[] allcertificates = certificates.listFiles();
if ( allcertificates != null) {
for (int i = 0; i < allcertificates.length; i++) {
File f = allcertificates[i];
for (File f : allcertificates) {
Util.d("Deleting old certificate file/dir " + f);
FileUtil.rmdir(f, false);
}
@ -231,41 +226,13 @@ class InitActivities {
/**
* Load defaults from resource,
* then add props from settings,
* and write back
* and write back.
*
* @param f relative to base dir
* @param overrides local overrides or null
*/
public void mergeResourceToFile(int resID, String f, Properties overrides) {
InputStream in = null;
InputStream fin = null;
try {
in = ctx.getResources().openRawResource(resID);
Properties props = new OrderedProperties();
try {
fin = new FileInputStream(new File(myDir, f));
DataHelper.loadProps(props, fin);
Util.d("Merging resource into file " + f);
} catch (IOException ioe) {
Util.d("Creating file " + f + " from resource");
}
// write in default settings
DataHelper.loadProps(props, in);
// override with user settings
if (overrides != null)
props.putAll(overrides);
File path = new File(myDir, f);
DataHelper.storeProps(props, path);
Util.d("Saved " + props.size() +" properties in " + f);
} catch (IOException ioe) {
} catch (Resources.NotFoundException nfe) {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
}
private void mergeResourceToFile(int resID, String f, Properties overrides) {
Util.mergeResourceToFile(ctx, myDir, f, resID, overrides, null);
}
/**

View File

@ -1,6 +1,5 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import android.os.Bundle;
public class LicenseActivity extends I2PActivityBase {

View File

@ -5,7 +5,7 @@ import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import net.i2p.android.router.R;
import net.i2p.android.router.dialog.TextResourceDialog;
public class LicenseFragment extends ListFragment {
@ -28,7 +28,7 @@ public class LicenseFragment extends ListFragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, names));
setListAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, names));
}
@Override

View File

@ -1,36 +1,29 @@
package net.i2p.android.router;
import java.io.File;
import java.util.List;
import java.util.Properties;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.dialog.AboutDialog;
import net.i2p.android.router.dialog.TextResourceDialog;
import net.i2p.android.router.service.IRouterState;
import net.i2p.android.router.service.IRouterStateCallback;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.util.OrderedProperties;
import java.io.File;
import java.lang.ref.WeakReference;
public class MainActivity extends I2PActivityBase implements
MainFragment.RouterControlListener {
@ -88,7 +81,7 @@ public class MainActivity extends I2PActivityBase implements
private void autoStart() {
if (canStart()) {
if (Util.isConnected(this)) {
if (Connectivity.isConnected(this)) {
mAutoStartFromIntent = true;
onStartRouterClicked();
} else {
@ -140,18 +133,11 @@ public class MainActivity extends I2PActivityBase implements
dialog.show(getSupportFragmentManager(), "about");
return true;
// TODO: Unhide when Help page finished
//case R.id.menu_help:
// Intent hi = new Intent(MainActivity.this, HelpActivity.class);
// hi.putExtra(HelpActivity.REFERRER, "main");
// startActivity(hi);
// return true;
// TODO: Remove when help page finished
case R.id.menu_help_licenses:
Intent lic = new Intent(MainActivity.this, LicenseActivity.class);
startActivity(lic);
case R.id.menu_help:
Intent hi = new Intent(MainActivity.this, HelpActivity.class);
startActivity(hi);
return true;
case R.id.menu_help_release_notes:
TextResourceDialog rDdialog = new TextResourceDialog();
Bundle args = new Bundle();
@ -249,24 +235,35 @@ public class MainActivity extends I2PActivityBase implements
private static final int STATE_MSG = 1;
private static final String MSG_DATA = "state";
private Handler mHandler = new Handler() {
private Handler mHandler = new StateHandler(new WeakReference<>(this));
private static class StateHandler extends Handler {
WeakReference<MainActivity> mReference;
public StateHandler(WeakReference<MainActivity> reference) {
mReference = reference;
}
private State lastRouterState = null;
@Override
public void handleMessage(Message msg) {
MainActivity parent = mReference.get();
if (parent == null)
return;
switch (msg.what) {
case STATE_MSG:
State state = msg.getData().getParcelable(MSG_DATA);
if (lastRouterState == null || lastRouterState != state) {
if (mMainFragment == null)
mMainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (mMainFragment != null) {
mMainFragment.updateState(state);
if (parent.mMainFragment == null)
parent.mMainFragment = (MainFragment) parent.getSupportFragmentManager().findFragmentById(R.id.main_fragment);
if (parent.mMainFragment != null) {
parent.mMainFragment.updateState(state);
lastRouterState = state;
}
if (state == State.RUNNING && mAutoStartFromIntent) {
setResult(RESULT_OK);
finish();
if (state == State.RUNNING && parent.mAutoStartFromIntent) {
parent.setResult(RESULT_OK);
parent.finish();
}
}
break;
@ -274,7 +271,7 @@ public class MainActivity extends I2PActivityBase implements
super.handleMessage(msg);
}
}
};
}
private boolean canStart() {
RouterService svc = _routerService;
@ -289,7 +286,7 @@ public class MainActivity extends I2PActivityBase implements
// MainFragment.RouterControlListener
public boolean shouldShowOnOff() {
return (canStart() && Util.isConnected(this)) || canStop();
return (canStart() && Connectivity.isConnected(this)) || canStop();
}
public boolean shouldBeOn() {
@ -304,7 +301,7 @@ public class MainActivity extends I2PActivityBase implements
setPref(PREF_AUTO_START, true);
svc.manualStart();
} else {
(new File(_myDir, "wrapper.log")).delete();
(new File(Util.getFileDir(this), "wrapper.log")).delete();
startRouter();
}
}

View File

@ -2,9 +2,11 @@ package net.i2p.android.router;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -16,9 +18,11 @@ import android.widget.TableRow;
import android.widget.TextView;
import android.widget.ToggleButton;
import net.i2p.android.router.dialog.ConfigureBrowserDialog;
import net.i2p.android.router.dialog.FirstStartDialog;
import net.i2p.android.router.dialog.VersionDialog;
import net.i2p.android.router.service.State;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.LongToggleButton;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
@ -44,6 +48,7 @@ public class MainFragment extends I2PFragmentBase {
private String _savedStatus;
private boolean _keep = true;
private boolean _startPressed = false;
private static final String PREF_CONFIGURE_BROWSER = "app.dialog.configureBrowser";
private static final String PREF_FIRST_START = "app.router.firstStart";
private static final String PREF_SHOW_STATS = "i2pandroid.main.showStats";
protected static final String PROP_NEW_INSTALL = "i2p.newInstall";
@ -205,7 +210,7 @@ public class MainFragment extends I2PFragmentBase {
public boolean onBackPressed() {
RouterContext ctx = getRouterContext();
// RouterService svc = _routerService; Which is better to use?!
_keep = Util.isConnected(getActivity()) && (ctx != null || _startPressed);
_keep = Connectivity.isConnected(getActivity()) && (ctx != null || _startPressed);
Util.d("*********************************************************");
Util.d("Back pressed, Keep? " + _keep);
Util.d("*********************************************************");
@ -264,7 +269,7 @@ public class MainFragment extends I2PFragmentBase {
LinearLayout vStatus = (LinearLayout) getActivity().findViewById(R.id.main_status);
TextView vStatusText = (TextView) getActivity().findViewById(R.id.main_status_text);
if(!Util.isConnected(getActivity())) {
if(!Connectivity.isConnected(getActivity())) {
// Manually set state, RouterService won't be running
updateState(State.WAITING);
vStatusText.setText("No Internet connection is available");
@ -397,24 +402,24 @@ public class MainFragment extends I2PFragmentBase {
String name = getName(ctx, client);
Hash h = client.calculateHash();
TableRow dest = new TableRow(getActivity());
//dest.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
dest.setPadding(16, 4, 0, 4);
// Client or server
ImageView type = new ImageView(getActivity());
type.setPadding(6, 6, 6, 6);
TextView type = new TextView(getActivity());
type.setTextColor(getResources().getColor(android.R.color.primary_text_light));
type.setTypeface(Typeface.DEFAULT_BOLD);
type.setGravity(Gravity.CENTER);
if (ctx.clientManager().shouldPublishLeaseSet(h))
type.setImageDrawable(getActivity().getResources()
.getDrawable(R.drawable.server));
type.setText(R.string.char_server_tunnel);
else
type.setImageDrawable(getActivity().getResources()
.getDrawable(R.drawable.client));
type.setText(R.string.char_client_tunnel);
dest.addView(type);
// Name
TextView destName = new TextView(getActivity());
destName.setPadding(6, 0, 0, 0);
destName.setPadding(16, 0, 0, 0);
destName.setGravity(Gravity.CENTER_VERTICAL);
destName.setText(name);
//destName.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
dest.addView(destName);
// Status
@ -423,14 +428,14 @@ public class MainFragment extends I2PFragmentBase {
long timeToExpire = ls.getEarliestLeaseDate() - ctx.clock().now();
if (timeToExpire < 0) {
// red or yellow light
type.setBackgroundColor(Color.TRANSPARENT);
type.setBackgroundResource(R.drawable.tunnel_yellow);
} else {
// green light
type.setBackgroundColor(Color.GREEN);
type.setBackgroundResource(R.drawable.tunnel_green);
}
} else {
// yellow light
type.setBackgroundColor(Color.TRANSPARENT);
type.setBackgroundResource(R.drawable.tunnel_yellow);
}
dests.addView(dest);
@ -438,7 +443,7 @@ public class MainFragment extends I2PFragmentBase {
} else {
TableRow empty = new TableRow(getActivity());
TextView emptyText = new TextView(getActivity());
emptyText.setText("No client tunnels are running yet.");
emptyText.setText(R.string.no_client_tunnels_running);
empty.addView(emptyText);
dests.addView(empty);
}
@ -487,14 +492,23 @@ public class MainFragment extends I2PFragmentBase {
}
private void checkDialog() {
VersionDialog dialog = new VersionDialog();
I2PActivityBase ab = (I2PActivityBase) getActivity();
boolean configureBrowser = ab.getPref(PREF_CONFIGURE_BROWSER, true);
if (configureBrowser) {
ConfigureBrowserDialog dialog = new ConfigureBrowserDialog();
dialog.show(getActivity().getSupportFragmentManager(), "configurebrowser");
ab.setPref(PREF_CONFIGURE_BROWSER, false);
}
/*VersionDialog dialog = new VersionDialog();
String oldVersion = ((I2PActivityBase) getActivity()).getPref(PREF_INSTALLED_VERSION, "??");
if(oldVersion.equals("??")) {
// TODO Don't show this dialog until it is reworked
Bundle args = new Bundle();
args.putInt(VersionDialog.DIALOG_TYPE, VersionDialog.DIALOG_NEW_INSTALL);
dialog.setArguments(args);
dialog.show(getActivity().getSupportFragmentManager(), "newinstall");
} else {
// TODO Don't show dialog on new version until we have something new to tell them
String currentVersion = Util.getOurVersion(getActivity());
if(!oldVersion.equals(currentVersion)) {
Bundle args = new Bundle();
@ -502,7 +516,7 @@ public class MainFragment extends I2PFragmentBase {
dialog.setArguments(args);
dialog.show(getActivity().getSupportFragmentManager(), "newversion");
}
}
}*/
}
private void checkFirstStart() {

View File

@ -1,6 +1,5 @@
package net.i2p.android.router;
import net.i2p.android.router.R;
import android.os.Bundle;
public class NewsActivity extends I2PActivityBase {

View File

@ -2,77 +2,57 @@ package net.i2p.android.router;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.TextView;
import net.i2p.android.apps.NewsFetcher;
import net.i2p.android.router.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import net.i2p.android.apps.NewsFetcher;
import net.i2p.android.router.R;
import net.i2p.android.router.web.I2PWebViewClient;
public class NewsFragment extends I2PFragmentBase {
private I2PWebViewClient _wvClient;
private long _lastChanged;
private static final String WARNING = "Warning - while the news is fetched over I2P, " +
"any non-I2P links visited in this window are fetched over the regular internet and are " +
"not anonymous.\n";
// TODO add some inline style
private static final String HEADER = "<html><head></head><body>";
private static final String FOOTER = "</body></html>";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_news, container, false);
WebView wv = (WebView) v.findViewById(R.id.news_webview);
wv.getSettings().setLoadsImagesAutomatically(false);
// http://stackoverflow.com/questions/2369310/webview-double-tap-zoom-not-working-on-a-motorola-droid-a855
wv.getSettings().setUseWideViewPort(true);
_wvClient = new I2PWebViewClient();
wv.setWebViewClient(_wvClient);
wv.getSettings().setBuiltInZoomControls(true);
return v;
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_news, container, false);
}
@Override
public void onResume()
{
public void onResume() {
super.onResume();
NewsFetcher nf = NewsFetcher.getInstance();
if (nf != null) {
// always update the text
// Always update the status
TextView tv = (TextView) getActivity().findViewById(R.id.news_status);
tv.setText(WARNING + nf.status().replace("&nbsp;", " "));
tv.setText(nf.status().replace("&nbsp;", " "));
tv.setVisibility(View.VISIBLE);
}
// only update the webview if we need to
// XXX Gets dir directly instead of the one stored in the Activity (for now)
File newsFile = new File(getActivity().getFilesDir().getAbsolutePath(), "docs/news.xml");
// Only update the content if we need to
File newsFile = new File(Util.getFileDir(getActivity()), "docs/news.xml");
boolean newsExists = newsFile.exists();
if (_lastChanged > 0 && ((!newsExists) || newsFile.lastModified() < _lastChanged))
return;
_lastChanged = System.currentTimeMillis();
WebView wv = (WebView) getActivity().findViewById(R.id.news_webview);
InputStream in = null;
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
byte buf[] = new byte[1024];
try {
if (newsExists) {
out.write(HEADER.getBytes());
in = new FileInputStream(newsFile);
} else {
in = getResources().openRawResource(R.raw.initialnews_html);
@ -82,30 +62,34 @@ public class NewsFragment extends I2PFragmentBase {
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
if (newsExists)
out.write(FOOTER.getBytes());
} catch (IOException ioe) {
System.err.println("news error " + ioe);
} catch (Resources.NotFoundException nfe) {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
String news = "";
try {
String news = out.toString("UTF-8");
wv.loadData(news, "text/html", "UTF-8");
news = out.toString("UTF-8");
} catch (UnsupportedEncodingException uee) {
}
}
public boolean onBackPressed() {
WebView wv = (WebView) getActivity().findViewById(R.id.news_webview);
_wvClient.cancelAll();
wv.stopLoading();
if (wv.canGoBack()) {
wv.goBack();
return true;
// Get SpannableStringBuilder object from HTML code
CharSequence sequence = Html.fromHtml(news);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
// Get an array of URLSpan from SpannableStringBuilder object
URLSpan[] urlSpans = strBuilder.getSpans(0, strBuilder.length(), URLSpan.class);
// Remove URLSpans with relative paths, which can't be clicked on
for (final URLSpan span : urlSpans) {
if (span.getURL().startsWith("/"))
strBuilder.removeSpan(span);
}
return false;
TextView tv = (TextView) getActivity().findViewById(R.id.news_content);
tv.setText(strBuilder);
tv.setMovementMethod(LinkMovementMethod.getInstance());
}
}

View File

@ -6,24 +6,21 @@ import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.util.PortPreference;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.stat.FrequencyStat;
@ -31,7 +28,12 @@ import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.stat.StatManager;
import net.i2p.util.LogManager;
import net.i2p.util.OrderedProperties;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
public class SettingsActivity extends PreferenceActivity {
// Actions for legacy settings
@ -40,6 +42,8 @@ public class SettingsActivity extends PreferenceActivity {
private static final String ACTION_PREFS_LOGGING = "net.i2p.android.router.PREFS_LOGGING";
private static final String ACTION_PREFS_ADVANCED = "net.i2p.android.router.PREFS_ADVANCED";
private Toolbar mToolbar;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -47,31 +51,59 @@ public class SettingsActivity extends PreferenceActivity {
String action = getIntent().getAction();
if (action != null) {
if (ACTION_PREFS_NET.equals(action)) {
addPreferencesFromResource(R.xml.settings_net);
} else if (ACTION_PREFS_GRAPHS.equals(action)){
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(this, getPreferenceScreen(), getRouterContext());
} else if (ACTION_PREFS_LOGGING.equals(action)) {
addPreferencesFromResource(R.xml.settings_logging);
RouterContext ctx = getRouterContext();
if (ctx != null)
setupLoggingSettings(this, getPreferenceScreen(), ctx);
} else if (ACTION_PREFS_ADVANCED.equals(action)) {
addPreferencesFromResource(R.xml.settings_advanced);
switch (action) {
case ACTION_PREFS_NET:
addPreferencesFromResource(R.xml.settings_net);
break;
case ACTION_PREFS_GRAPHS:
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(this, getPreferenceScreen(), Util.getRouterContext());
break;
case ACTION_PREFS_LOGGING:
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(this, getPreferenceScreen(), Util.getRouterContext());
break;
case ACTION_PREFS_ADVANCED:
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(this, getPreferenceScreen());
break;
}
} else {
// Load any properties that the router might have changed on us.
setupPreferences(this, Util.getRouterContext());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.settings_headers_legacy);
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.settings_headers_legacy);
}
mToolbar.setTitle(getTitle());
}
protected static RouterContext getRouterContext() {
List<RouterContext> contexts = RouterContext.listContexts();
if ( !((contexts == null) || (contexts.isEmpty())) ) {
return contexts.get(0);
protected static void setupPreferences(Context context, RouterContext ctx) {
if (ctx != null) {
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
int udpPort = ctx.getProperty(udpPortKey, -1);
int ntcpPort = ctx.getProperty(ntcpPortKey, -1);
boolean ntcpAutoPort = ctx.getBooleanPropertyDefaultTrue(ntcpAutoPortKey);
if (ntcpPort < 0 && ntcpAutoPort)
ntcpPort = udpPort;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getInt(udpPortKey, -1) != udpPort ||
prefs.getInt(ntcpPortKey, -1) != ntcpPort) {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(udpPortKey, udpPort);
editor.putInt(ntcpPortKey, ntcpPort);
// commit() instead of apply() because this needs to happen
// before SettingsActivity loads its Preferences.
editor.commit();
}
}
return null;
}
protected static void setupGraphSettings(Context context, PreferenceScreen ps, RouterContext ctx) {
@ -153,13 +185,74 @@ public class SettingsActivity extends PreferenceActivity {
buf.append(prefix).append('=').append(level).append('\n');
}
*/
/* Don't show, there are no settings that require the router
} else {
PreferenceCategory noRouter = new PreferenceCategory(context);
noRouter.setTitle(R.string.router_not_running);
ps.addPreference(noRouter);
*/
}
}
protected static void setupAdvancedSettings(final Context context, PreferenceScreen ps) {
final String udpEnableKey = context.getString(R.string.PROP_ENABLE_UDP);
final String ntcpEnableKey = context.getString(R.string.PROP_ENABLE_NTCP);
final String udpPortKey = context.getString(R.string.PROP_UDP_INTERNAL_PORT);
final String ntcpPortKey = context.getString(R.string.PROP_I2NP_NTCP_PORT);
final String ntcpAutoPortKey = context.getString(R.string.PROP_I2NP_NTCP_AUTO_PORT);
final CheckBoxPreference udpEnable = (CheckBoxPreference) ps.findPreference(udpEnableKey);
final CheckBoxPreference ntcpEnable = (CheckBoxPreference) ps.findPreference(ntcpEnableKey);
final PortPreference udpPort = (PortPreference) ps.findPreference(udpPortKey);
final PortPreference ntcpPort = (PortPreference) ps.findPreference(ntcpPortKey);
final CheckBoxPreference ntcpAutoPort = (CheckBoxPreference) ps.findPreference(ntcpAutoPortKey);
udpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked || ntcpEnable.isChecked())
return true;
else {
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
return false;
}
}
});
ntcpEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked || udpEnable.isChecked())
return true;
else {
Toast.makeText(context, R.string.settings_need_transport_enabled, Toast.LENGTH_LONG).show();
return false;
}
}
});
udpPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (ntcpAutoPort.isChecked())
ntcpPort.setText((String) newValue);
return true;
}
});
ntcpAutoPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Boolean checked = (Boolean) newValue;
if (checked)
ntcpPort.setText(udpPort.getText());
return true;
}
});
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
@ -170,24 +263,48 @@ public class SettingsActivity extends PreferenceActivity {
loadHeadersFromResource(R.xml.settings_headers, target);
}
@Override
public void setContentView(int layoutResID) {
ViewGroup contentView = (ViewGroup) LayoutInflater.from(this).inflate(
R.layout.activity_settings,
(ViewGroup) getWindow().getDecorView().getRootView(), false);
mToolbar = (Toolbar) contentView.findViewById(R.id.main_toolbar);
mToolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
ViewGroup contentWrapper = (ViewGroup) contentView.findViewById(R.id.content_wrapper);
LayoutInflater.from(this).inflate(layoutResID, contentWrapper, true);
getWindow().setContentView(contentView);
}
@Override
protected void onPause() {
List<Properties> lProps = Util.getPropertiesFromPreferences(this);
Properties props = lProps.get(0);
Properties logSettings = lProps.get(1);
Properties propsToRemove = lProps.get(1);
Properties logSettings = lProps.get(2);
Set toRemove = propsToRemove.keySet();
boolean restartRequired = Util.checkAndCorrectRouterConfig(this, props, toRemove);
// Apply new config if we are running.
List<RouterContext> contexts = RouterContext.listContexts();
if ( !((contexts == null) || (contexts.isEmpty())) ) {
RouterContext _context = contexts.get(0);
_context.router().saveConfig(props, null);
RouterContext rCtx = Util.getRouterContext();
if (rCtx != null) {
rCtx.router().saveConfig(props, toRemove);
// Merge in new log settings
saveLoggingChanges(_context, logSettings);
saveLoggingChanges(rCtx, logSettings);
} else {
// Merge in new config settings, write the file.
InitActivities init = new InitActivities(this);
init.mergeResourceToFile(R.raw.router_config, "router.config", props);
Util.mergeResourceToFile(this, Util.getFileDir(this), "router.config", R.raw.router_config, props, toRemove);
// Merge in new log settings
saveLoggingChanges(I2PAppContext.getGlobalContext(), logSettings);
@ -195,6 +312,9 @@ public class SettingsActivity extends PreferenceActivity {
// Store the settings in Android
super.onPause();
if (restartRequired)
Toast.makeText(this, R.string.settings_router_restart_required, Toast.LENGTH_LONG).show();
}
private void saveLoggingChanges(I2PAppContext ctx, Properties logSettings) {
@ -223,20 +343,22 @@ public class SettingsActivity extends PreferenceActivity {
super.onCreate(savedInstanceState);
String settings = getArguments().getString("settings");
if ("net".equals(settings)) {
addPreferencesFromResource(R.xml.settings_net);
} else if ("graphs".equals(settings)) {
addPreferencesFromResource(R.xml.settings_graphs);
RouterContext ctx = getRouterContext();
if (ctx != null)
setupGraphSettings(getActivity(), getPreferenceScreen(), ctx);
} else if ("logging".equals(settings)) {
addPreferencesFromResource(R.xml.settings_logging);
RouterContext ctx = getRouterContext();
if (ctx != null)
setupLoggingSettings(getActivity(), getPreferenceScreen(), ctx);
} else if ("advanced".equals(settings)) {
addPreferencesFromResource(R.xml.settings_advanced);
switch (settings) {
case "net":
addPreferencesFromResource(R.xml.settings_net);
break;
case "graphs":
addPreferencesFromResource(R.xml.settings_graphs);
setupGraphSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
break;
case "logging":
addPreferencesFromResource(R.xml.settings_logging);
setupLoggingSettings(getActivity(), getPreferenceScreen(), Util.getRouterContext());
break;
case "advanced":
addPreferencesFromResource(R.xml.settings_advanced);
setupAdvancedSettings(getActivity(), getPreferenceScreen());
break;
}
}
}

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,42 +1,47 @@
package net.i2p.android.router.addressbook;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.android.router.util.Util;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.android.router.util.Util;
import net.i2p.client.naming.NamingService;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public class AddressEntryLoader extends AsyncTaskLoader<List<AddressEntry>> {
private RouterContext mRContext;
private I2PFragmentBase.RouterContextProvider mRContextProvider;
private String mBook;
private String mFilter;
private List<AddressEntry> mData;
public AddressEntryLoader(Context context, RouterContext rContext,
public AddressEntryLoader(Context context, I2PFragmentBase.RouterContextProvider rContextProvider,
String book, String filter) {
super(context);
mRContext = rContext;
mRContextProvider = rContextProvider;
mBook = book;
mFilter = filter;
}
@Override
public List<AddressEntry> loadInBackground() {
RouterContext routerContext = mRContextProvider.getRouterContext();
if (routerContext == null)
return null;
// get the names
NamingService ns = NamingServiceUtil.getNamingService(mRContext, mBook);
NamingService ns = NamingServiceUtil.getNamingService(routerContext, mBook);
Util.d("NamingService: " + ns.getName());
// After router shutdown we get nothing... why?
List<AddressEntry> ret = new ArrayList<AddressEntry>();
Map<String, Destination> names = new TreeMap<String, Destination>();
List<AddressEntry> ret = new ArrayList<>();
Map<String, Destination> names = new TreeMap<>();
Properties searchProps = new Properties();
// Needed for HostsTxtNamingService

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

@ -6,6 +6,7 @@ import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.wizard.model.AbstractWizardModel;
import net.i2p.android.wizard.ui.AbstractWizardActivity;
@ -19,6 +20,7 @@ public class AddressbookAddWizardActivity extends AbstractWizardActivity {
@Override
protected DialogFragment onGetFinishWizardDialog() {
return new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())

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.router.HelpActivity;
import net.i2p.android.help.HelpActivity;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import net.i2p.android.router.I2PFragmentBase.RouterContextProvider;
import net.i2p.android.router.R;
import net.i2p.android.router.util.NamingServiceUtil;
import net.i2p.client.naming.NamingService;
import net.i2p.router.RouterContext;
import java.util.List;
public class AddressbookFragment extends ListFragment implements
I2PFragmentBase.RouterContextUser,
LoaderManager.LoaderCallbacks<List<AddressEntry>> {
@ -46,6 +50,8 @@ public class AddressbookFragment extends ListFragment implements
private String mBook;
private String mCurFilter;
private ImageButton mAddToAddressbook;
// Set in onActivityResult()
private Intent mAddWizardData;
@ -84,6 +90,27 @@ public class AddressbookFragment extends ListFragment implements
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Create the list fragment's content view by calling the super method
final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_list_with_add, container, false);
FrameLayout listContainer = (FrameLayout) v.findViewById(R.id.list_container);
listContainer.addView(listFragmentView);
mAddToAddressbook = (ImageButton) v.findViewById(R.id.promoted_action);
mAddToAddressbook.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
startActivityForResult(wi, ADD_WIZARD_REQUEST);
}
});
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@ -111,8 +138,8 @@ public class AddressbookFragment extends ListFragment implements
// Show actions
if (mSearchAddressbook != null)
mSearchAddressbook.setVisible(true);
if (mAddToAddressbook != null)
mAddToAddressbook.setVisible(false);
if (mAddToAddressbook != null && mAddToAddressbook.getVisibility() != View.VISIBLE)
mAddToAddressbook.setVisibility(View.VISIBLE);
if (mAddWizardData != null) {
// Save the new entry
@ -140,24 +167,26 @@ public class AddressbookFragment extends ListFragment implements
}
private MenuItem mSearchAddressbook;
private MenuItem mAddToAddressbook;
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_addressbook_actions, menu);
mSearchAddressbook = menu.findItem(R.id.action_search_addressbook);
mAddToAddressbook = menu.findItem(R.id.action_add_to_addressbook);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
// Hide until needed
if (getRouterContext() == null) {
mSearchAddressbook.setVisible(false);
mAddToAddressbook.setVisible(false);
if (mAddToAddressbook != null)
mAddToAddressbook.setVisibility(View.GONE);
}
// Only allow adding to private book
if (!PRIVATE_BOOK.equals(mBook)) {
mAddToAddressbook.setVisible(false);
if (!PRIVATE_BOOK.equals(mBook) && mAddToAddressbook != null) {
mAddToAddressbook.setVisibility(View.GONE);
mAddToAddressbook = null;
}
}
@ -167,27 +196,22 @@ public class AddressbookFragment extends ListFragment implements
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_add_to_addressbook:
Intent wi = new Intent(getActivity(), AddressbookAddWizardActivity.class);
startActivityForResult(wi, ADD_WIZARD_REQUEST);
return true;
case R.id.action_reload_subscriptions:
Daemon.wakeup();
Toast.makeText(getActivity(), "Reloading subscriptions...",
Toast.LENGTH_SHORT).show();
return true;
case R.id.action_addressbook_settings:
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
startActivity(si);
return true;
// TODO: Enable when Help page finished
//case R.id.action_addressbook_help:
// Intent hi = new Intent(getActivity(), HelpActivity.class);
// hi.putExtra(HelpActivity.REFERRER, "addressbook");
// startActivity(hi);
// return true;
default:
return super.onOptionsItemSelected(item);
case R.id.action_reload_subscriptions:
Daemon.wakeup();
Toast.makeText(getActivity(), "Reloading subscriptions...",
Toast.LENGTH_SHORT).show();
return true;
case R.id.action_addressbook_settings:
Intent si = new Intent(getActivity(), AddressbookSettingsActivity.class);
startActivity(si);
return true;
case R.id.action_addressbook_help:
Intent hi = new Intent(getActivity(), HelpActivity.class);
hi.putExtra(HelpActivity.CATEGORY, HelpActivity.CAT_ADDRESSBOOK);
startActivity(hi);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@ -225,27 +249,32 @@ public class AddressbookFragment extends ListFragment implements
public Loader<List<AddressEntry>> onCreateLoader(int id, Bundle args) {
return new AddressEntryLoader(getActivity(),
getRouterContext(), mBook, mCurFilter);
mRouterContextProvider, mBook, mCurFilter);
}
public void onLoadFinished(Loader<List<AddressEntry>> loader,
List<AddressEntry> data) {
List<AddressEntry> data) {
if (loader.getId() == (PRIVATE_BOOK.equals(mBook) ?
PRIVATE_LOADER_ID : ROUTER_LOADER_ID)) {
mAdapter.setData(data);
if (data == null)
setEmptyText(getResources().getString(
R.string.router_not_running));
else {
mAdapter.setData(data);
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
if (mCurFilter != null)
v.setText(getActivity().getResources().getString(
R.string.addressbook_search_header,
data.size()));
else
v.setText("");
TextView v = (TextView) getListView().findViewWithTag("addressbook_header");
if (mCurFilter != null)
v.setText(getActivity().getResources().getString(
R.string.addressbook_search_header,
data.size()));
else
v.setText("");
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
}
}

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

@ -1,18 +1,24 @@
package net.i2p.android.router.dialog;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import net.i2p.android.router.util.Util;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.router.LicenseActivity;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import net.i2p.android.router.util.Util;
public class AboutDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
@ -33,7 +39,14 @@ public class AboutDialog extends DialogFragment {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.menu_about)
.setView(view);
.setView(view)
.setNeutralButton(R.string.label_licenses, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent lic = new Intent(getActivity(), LicenseActivity.class);
startActivity(lic);
}
});
return b.create();
}
}

View File

@ -0,0 +1,38 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.help.BrowserConfigActivity;
import net.i2p.android.router.R;
public class ConfigureBrowserDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.configure_browser_title)
.setMessage(R.string.configure_browser_for_i2p)
.setCancelable(false)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Intent hi = new Intent(getActivity(), BrowserConfigActivity.class);
startActivity(hi);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
});
return b.create();
}
}

View File

@ -1,19 +1,22 @@
package net.i2p.android.router.dialog;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.router.util.I2Patterns;
public class FirstStartDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater li = LayoutInflater.from(getActivity());
View view = li.inflate(R.layout.fragment_dialog_first_start, null);
@ -24,7 +27,7 @@ public class FirstStartDialog extends DialogFragment {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
b.setTitle(R.string.first_start_title)
.setView(view);
.setView(view);
return b.create();
}
}

View File

@ -3,17 +3,18 @@ package net.i2p.android.router.dialog;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
/**
* Display a raw text resource.
@ -30,7 +31,6 @@ public class TextResourceDialog extends DialogFragment {
{
View v = inflater.inflate(R.layout.fragment_dialog_text_resource, container, false);
TextView tv = (TextView) v.findViewById(R.id.text_resource_text);
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
String title = getArguments().getString(TEXT_DIALOG_TITLE);
if (title != null)
getDialog().setTitle(title);
@ -54,10 +54,8 @@ public class TextResourceDialog extends DialogFragment {
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
} catch (IOException ioe) {
System.err.println("resource error " + ioe);
} catch (Resources.NotFoundException nfe) {
System.err.println("resource error " + nfe);
} catch (IOException | Resources.NotFoundException re) {
System.err.println("resource error " + re);
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}

View File

@ -1,24 +1,27 @@
package net.i2p.android.router.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.MainFragment;
import net.i2p.android.router.R;
import net.i2p.android.router.util.Util;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
public class VersionDialog extends DialogFragment {
public static final String DIALOG_TYPE = "dialog_type";
public static final int DIALOG_NEW_INSTALL = 0;
public static final int DIALOG_NEW_VERSION = 1;
@NonNull
@Override
public Dialog onCreateDialog(Bundle SavedInstanceState) {
final String currentVersion = Util.getOurVersion(getActivity());
Dialog rv = null;
Dialog rv;
AlertDialog.Builder b = new AlertDialog.Builder(getActivity());
int id = getArguments().getInt(DIALOG_TYPE);
switch(id) {
@ -53,6 +56,7 @@ public class VersionDialog extends DialogFragment {
break;
case DIALOG_NEW_VERSION:
default:
b.setMessage(getResources().getString(R.string.welcome_new_version) +
" " + currentVersion)
.setCancelable(true)

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

@ -2,12 +2,19 @@ package net.i2p.android.router.log;
import net.i2p.android.router.I2PFragmentBase;
import net.i2p.android.router.R;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
public class LogDetailFragment extends I2PFragmentBase {
public static final String LOG_ENTRY = "log_entry";
@ -22,6 +29,12 @@ public class LogDetailFragment extends I2PFragmentBase {
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -34,4 +47,31 @@ public class LogDetailFragment extends I2PFragmentBase {
return v;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_log_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_copy_logs:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mEntry);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
getString(R.string.i2p_android_logs), mEntry);
clipboard.setPrimaryClip(clip);
}
Toast.makeText(getActivity(), R.string.logs_copied_to_clipboard, Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -1,17 +1,26 @@
package net.i2p.android.router.log;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
import android.widget.Toast;
import net.i2p.I2PAppContext;
import net.i2p.android.router.R;
import java.util.ArrayList;
import java.util.List;
public class LogFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<String>> {
public static final String LOG_LEVEL = "log_level";
@ -25,6 +34,7 @@ public class LogFragment extends ListFragment implements
private static final int LEVEL_ALL = 2;
OnEntrySelectedListener mEntrySelectedCallback;
private final List<String> mLogEntries = new ArrayList<>();
private LogAdapter mAdapter;
private TextView mHeaderView;
private String mLogLevel;
@ -34,6 +44,8 @@ public class LogFragment extends ListFragment implements
private int mActivatedPosition = ListView.INVALID_POSITION;
private boolean mActivateOnItemClick = false;
private MenuItem mCopyLogs;
// Container Activity must implement this interface
public interface OnEntrySelectedListener {
public void onEntrySelected(String entry);
@ -62,6 +74,12 @@ public class LogFragment extends ListFragment implements
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@ -122,6 +140,53 @@ public class LogFragment extends ListFragment implements
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_log_actions, menu);
mCopyLogs = menu.findItem(R.id.action_copy_logs);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
mCopyLogs.setVisible(I2PAppContext.getCurrentContext() != null);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_copy_logs:
String logText = "";
synchronized (mLogEntries) {
for (String logEntry : mLogEntries) {
logText += logEntry;
}
}
boolean isError = "ERROR".equals(mLogLevel);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(logText);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
isError ? getString(R.string.i2p_android_error_logs) : getString(R.string.i2p_android_logs),
logText);
clipboard.setPrimaryClip(clip);
}
int textId;
if (isError)
textId = R.string.error_logs_copied_to_clipboard;
else
textId = R.string.logs_copied_to_clipboard;
Toast.makeText(getActivity(), textId, Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
@ -167,6 +232,10 @@ public class LogFragment extends ListFragment implements
List<String> data) {
if (loader.getId() == ("ERROR".equals(mLogLevel) ?
LEVEL_ERROR : LEVEL_ALL)) {
synchronized (mLogEntries) {
mLogEntries.clear();
mLogEntries.addAll(data);
}
mAdapter.setData(data);
String header = getHeader(data.size(), ("ERROR".equals(mLogLevel)));
mHeaderView.setText(header);

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

@ -1,18 +1,19 @@
package net.i2p.android.router.netdb;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import net.i2p.data.Destination;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.router.RouterContext;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
private RouterContext mRContext;
private boolean mRouters;
@ -44,17 +45,17 @@ public class NetDbEntryLoader extends AsyncTaskLoader<List<NetDbEntry>> {
@Override
public List<NetDbEntry> loadInBackground() {
List<NetDbEntry> ret = new ArrayList<NetDbEntry>();
List<NetDbEntry> ret = new ArrayList<>();
if (mRContext.netDb().isInitialized()) {
if (mRouters) {
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
routers.addAll(mRContext.netDb().getRouters());
for (RouterInfo ri : routers) {
NetDbEntry entry = NetDbEntry.fromRouterInfo(mRContext, ri);
ret.add(entry);
}
} else {
Set<LeaseSet> leases = new TreeSet<LeaseSet>(new LeaseSetComparator());
Set<LeaseSet> leases = new TreeSet<>(new LeaseSetComparator());
leases.addAll(mRContext.netDb().getLeases());
for (LeaseSet ls : leases) {
NetDbEntry entry = NetDbEntry.fromLeaseSet(mRContext, ls);

View File

@ -1,19 +1,20 @@
package net.i2p.android.router.netdb;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import net.i2p.data.Hash;
import net.i2p.data.router.RouterAddress;
import net.i2p.data.router.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.ObjectCounter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import net.i2p.data.Hash;
import net.i2p.data.RouterAddress;
import net.i2p.data.RouterInfo;
import net.i2p.router.RouterContext;
import net.i2p.util.ObjectCounter;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>>> {
private RouterContext mRContext;
private List<ObjectCounter<String>> mData;
@ -31,16 +32,16 @@ public class NetDbStatsLoader extends AsyncTaskLoader<List<ObjectCounter<String>
@Override
public List<ObjectCounter<String>> loadInBackground() {
List<ObjectCounter<String>> ret = new ArrayList<ObjectCounter<String>>();
List<ObjectCounter<String>> ret = new ArrayList<>();
ObjectCounter<String> versions = new ObjectCounter<String>();
ObjectCounter<String> countries = new ObjectCounter<String>();
ObjectCounter<String> transports = new ObjectCounter<String>();
ObjectCounter<String> versions = new ObjectCounter<>();
ObjectCounter<String> countries = new ObjectCounter<>();
ObjectCounter<String> transports = new ObjectCounter<>();
if (mRContext.netDb().isInitialized()) {
if (mRContext != null && mRContext.netDb() != null && mRContext.netDb().isInitialized()) {
Hash us = mRContext.routerHash();
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
Set<RouterInfo> routers = new TreeSet<>(new RouterInfoComparator());
routers.addAll(mRContext.netDb().getRouters());
for (RouterInfo ri : routers) {
Hash key = ri.getHash();

View File

@ -1,12 +1,5 @@
package net.i2p.android.router.netdb;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.i2p.android.router.R;
import net.i2p.util.ObjectCounter;
import net.i2p.util.VersionComparator;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
@ -16,6 +9,14 @@ import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import net.i2p.android.router.R;
import net.i2p.util.ObjectCounter;
import net.i2p.util.VersionComparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NetDbSummaryTableFragment extends Fragment {
private static final String CATEGORY = "category";
private static final String COUNTS = "counts";
@ -44,7 +45,7 @@ public class NetDbSummaryTableFragment extends Fragment {
mTable = (TableLayout) v.findViewById(R.id.table);
List<String> objects = new ArrayList<String>(mCounts.objects());
List<String> objects = new ArrayList<>(mCounts.objects());
if (!objects.isEmpty()) {
createTableTitle();

View File

@ -6,6 +6,12 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.BuildConfig;
import net.i2p.android.router.util.AppCache;
import net.i2p.android.router.util.Util;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@ -13,9 +19,6 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.util.AppCache;
import net.i2p.android.router.util.Util;
/**
* Usage: content://net.i2p.android.router/NONCE/ENCODED-SCHEME/ENCODED-AUTHORITY/ENCODED_PATH + QUERY_MARKER + ENCODED-QUERY
@ -45,7 +48,7 @@ public class CacheProvider extends ContentProvider {
//private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt()));
private static final String NONCE = "0";
private static final String SCHEME = "content";
public static final String AUTHORITY = "net.i2p.android";
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
/** includes the nonce */
public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE);
@ -140,7 +143,7 @@ public class CacheProvider extends ContentProvider {
*
* @param uri must contain a scheme, authority and path with nonce etc. as defined above
* @return non-null
* @throws FNFE on error
* @throws java.io.FileNotFoundException on error
*/
public static Uri getI2PUri(Uri uri) throws FileNotFoundException {
String resPath = uri.getEncodedPath();
@ -242,8 +245,7 @@ public class CacheProvider extends ContentProvider {
if (file.length() > 0) {
// this call will insert it back to us (don't set as current base)
Uri content = cache.addCacheFile(uri, false);
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return parcel;
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} else {
Util.d("CacheProvider Sucess but no data " + uri);
}
@ -275,7 +277,7 @@ public class CacheProvider extends ContentProvider {
put(uri, fileURI);
}
Boolean setAsCurrentBase = values.getAsBoolean(CURRENT_BASE);
if (setAsCurrentBase != null && setAsCurrentBase.booleanValue()) {
if (setAsCurrentBase != null && setAsCurrentBase) {
Util.d("CacheProvider set current base " + uri);
setCurrentBase(uri);
}
@ -302,7 +304,7 @@ public class CacheProvider extends ContentProvider {
private void cleanup() {
String pfx = CONTENT_URI.toString();
List<String> toDelete = new ArrayList<String>();
List<String> toDelete = new ArrayList<>();
Map<String, ?> map = _sharedPrefs.getAll();
for (Map.Entry<String, ?> e : map.entrySet()) {
String path = (String) e.getValue();

View File

@ -11,6 +11,7 @@ import android.net.NetworkInfo;
import android.os.IBinder;
import net.i2p.android.router.service.RouterBinder;
import net.i2p.android.router.service.RouterService;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
public class I2PReceiver extends BroadcastReceiver {
@ -33,7 +34,7 @@ public class I2PReceiver extends BroadcastReceiver {
intents.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
@SuppressWarnings("LeakingThisInConstructor")
Intent registerReceiver = context.registerReceiver(this, intents);
_wasConnected = Util.isConnected(context);
_wasConnected = Connectivity.isConnected(context);
}
public void onReceive(Context context, Intent intent) {
@ -57,7 +58,7 @@ public class I2PReceiver extends BroadcastReceiver {
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
action.equals(Intent.ACTION_TIME_TICK)) {
boolean connected = Util.isConnected(context);
boolean connected = Connectivity.isConnected(context);
if (_wasConnected && !connected) {
// notify + 2 timer ticks
if (++_unconnectedCount >= 3) {

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,10 +1,12 @@
package net.i2p.android.router.service;
import android.content.Context;
import java.io.File;
import net.i2p.android.router.util.Util;
import net.i2p.util.FileUtil;
import java.io.File;
class Init {
private final Context ctx;
@ -32,8 +34,7 @@ class Init {
File tmp = new File(myDir, "tmp");
File[] files = tmp.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
for (File f : files) {
Util.d("Deleting old file/dir " + f);
FileUtil.rmdir(f, false);
}

View File

@ -9,17 +9,9 @@ import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import net.i2p.android.router.R;
import net.i2p.android.router.receiver.I2PReceiver;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Notifications;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
@ -27,7 +19,8 @@ import net.i2p.router.Job;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterLaunch;
import net.i2p.util.OrderedProperties;
import java.text.DecimalFormat;
/**
* Runs the router
@ -58,7 +51,7 @@ public class RouterService extends Service {
* that it can be accessed more efficiently from inner classes.
*/
final RemoteCallbackList<IRouterStateCallback> mStateCallbacks
= new RemoteCallbackList<IRouterStateCallback>();
= new RemoteCallbackList<>();
@Override
public void onCreate() {
@ -115,7 +108,7 @@ public class RouterService extends Service {
return START_NOT_STICKY;
}
_receiver = new I2PReceiver(this);
if(Util.isConnected(this)) {
if(Connectivity.isConnected(this)) {
if(restart) {
_statusBar.replace(StatusBar.ICON_STARTING, "I2P is restarting");
} else {
@ -149,7 +142,7 @@ public class RouterService extends Service {
Util.d(MARKER + this + " waiter handler"
+ " Current state is: " + _state);
if(_state == State.WAITING) {
if(Util.isConnected(RouterService.this)) {
if(Connectivity.isConnected(RouterService.this)) {
synchronized(_stateLock) {
if(_state != State.WAITING) {
return;
@ -175,159 +168,18 @@ public class RouterService extends Service {
//NativeBigInteger.main(null);
//Util.d(MARKER + this + " JBigI speed test finished, launching router");
// Before we launch, fix up any settings that need to be fixed here.
// This should be done in the core, but as of this writing it isn't!
// Step one. Load the propertites.
Properties props = new OrderedProperties();
Properties oldprops = new OrderedProperties();
String wrapName = _myDir + "/router.config";
try {
InputStream fin = new FileInputStream(new File(wrapName));
DataHelper.loadProps(props, fin);
} catch(IOException ioe) {
// shouldn't happen...
}
oldprops.putAll(props);
// Step two, check for any port settings, and copy for those that are missing.
int UDPinbound;
int UDPinlocal;
int TCPinbound;
int TCPinlocal;
UDPinbound = Integer.parseInt(props.getProperty("i2np.udp.port", "-1"));
UDPinlocal = Integer.parseInt(props.getProperty("i2np.udp.internalPort", "-1"));
TCPinbound = Integer.parseInt(props.getProperty("i2np.ntcp.port", "-1"));
TCPinlocal = Integer.parseInt(props.getProperty("i2np.ntcp.internalPort", "-1"));
boolean hasUDPinbound = UDPinbound != -1;
boolean hasUDPinlocal = UDPinlocal != -1;
boolean hasTCPinbound = TCPinbound != -1;
boolean hasTCPinlocal = TCPinlocal != -1;
// check and clear values based on these:
boolean udp = Boolean.parseBoolean(props.getProperty("i2np.udp.enable", "false"));
boolean tcp = Boolean.parseBoolean(props.getProperty("i2np.ntcp.enable", "false"));
// Fix if both are false.
if(!(udp || tcp)) {
// If both are not on, turn them both on.
props.setProperty("i2np.udp.enable", "true");
props.setProperty("i2np.ntcp.enable", "true");
}
// Fix if we have local but no inbound
if(!hasUDPinbound && hasUDPinlocal) {
// if we got a local port and no external port, set it
hasUDPinbound = true;
UDPinbound = UDPinlocal;
}
if(!hasTCPinbound && hasTCPinlocal) {
// if we got a local port and no external port, set it
hasTCPinbound = true;
TCPinbound = TCPinlocal;
}
boolean anyUDP = hasUDPinbound || hasUDPinlocal;
boolean anyTCP = hasTCPinbound || hasTCPinlocal;
boolean anyport = anyUDP || anyTCP;
if(!anyport) {
// generate one for UDPinbound, and fall thru.
// FIX ME: Possibly not the best but should be OK.
Random generator = new Random(System.currentTimeMillis());
UDPinbound = generator.nextInt(55500) + 10000;
anyUDP = true;
}
// Copy missing port numbers
if(anyUDP && !anyTCP) {
TCPinbound = UDPinbound;
TCPinlocal = UDPinlocal;
}
if(anyTCP && !anyUDP) {
UDPinbound = TCPinbound;
UDPinlocal = TCPinlocal;
}
// reset for a retest.
hasUDPinbound = UDPinbound != -1;
hasUDPinlocal = UDPinlocal != -1;
hasTCPinbound = TCPinbound != -1;
hasTCPinlocal = TCPinlocal != -1;
anyUDP = hasUDPinbound || hasUDPinlocal;
anyTCP = hasTCPinbound || hasTCPinlocal;
boolean checkAnyUDP = anyUDP && udp;
boolean checkAnyTCP = anyTCP && tcp;
// Enable things that need to be enabled.
// Disable anything that needs to be disabled.
if(!checkAnyUDP && !checkAnyTCP) {
// enable the one(s) with values.
if(anyUDP) {
udp = true;
}
if(anyTCP) {
tcp = true;
}
}
if(!udp) {
props.setProperty("i2np.udp.enable", "false");
props.remove("i2np.udp.port");
props.remove("i2np.udp.internalPort");
} else {
props.setProperty("i2np.udp.enable", "true");
if(hasUDPinbound) {
props.setProperty("i2np.udp.port", Integer.toString(UDPinbound));
} else {
props.remove("i2np.udp.port");
}
if(hasUDPinlocal) {
props.setProperty("i2np.udp.internalPort", Integer.toString(UDPinlocal));
} else {
props.remove("i2np.udp.internalPort");
}
}
if(!tcp) {
props.setProperty("i2np.ntcp.enable", "false");
props.remove("i2np.ntcp.port");
props.remove("i2np.ntcp.internalPort");
} else {
props.setProperty("i2np.ntcp.enable", "true");
if(hasTCPinbound) {
props.setProperty("i2np.ntcp.port", Integer.toString(TCPinbound));
} else {
props.remove("i2np.ntcp.port");
}
if(hasTCPinlocal) {
props.setProperty("i2np.ntcp.internalPort", Integer.toString(TCPinlocal));
} else {
props.remove("i2np.ntcp.internalPort");
}
}
// WHEW! Now test for any changes.
if(!props.equals(oldprops)) {
// save fixed properties.
try {
DataHelper.storeProps(props, new File(wrapName));
} catch(IOException ioe) {
// shouldn't happen...
}
}
// _NOW_ launch the router!
// Launch the router!
RouterLaunch.main(null);
synchronized(_stateLock) {
if(_state != State.STARTING) {
return;
}
setState(State.RUNNING);
List<?> contexts = RouterContext.listContexts();
if((contexts == null) || (contexts.isEmpty())) {
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
_context = Util.getRouterContext();
if (_context == null) {
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
}
_statusBar.replace(StatusBar.ICON_RUNNING, "I2P is running");
_context = (RouterContext) contexts.get(0);
_context.router().setKillVMOnEnd(false);
Job loadJob = new LoadClientsJob(_context, _notif);
_context.jobQueue().addJob(loadJob);
@ -457,7 +309,7 @@ public class RouterService extends Service {
// ******** following methods may be accessed from Activities and Receivers ************
/**
* @returns null if router is not running
* @return null if router is not running
*/
public RouterContext getRouterContext() {
RouterContext rv = _context;

View File

@ -1,14 +1,15 @@
package net.i2p.android.router.service;
import net.i2p.android.router.util.Util;
import net.i2p.router.RouterContext;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import net.i2p.router.RouterContext;
import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
public class StatSummarizer implements Runnable {
private final RouterContext _context;
private final List<SummaryListener> _listeners;
@ -18,15 +19,20 @@ public class StatSummarizer implements Runnable {
private Thread _thread;
public StatSummarizer() {
_context = RouterContext.listContexts().get(0);
_listeners = new CopyOnWriteArrayList<SummaryListener>();
_context = Util.getRouterContext();
_listeners = new CopyOnWriteArrayList<>();
_instance = this;
_context.addShutdownTask(new Shutdown());
if (_context != null)
_context.addShutdownTask(new Shutdown());
}
public static StatSummarizer instance() { return _instance; }
public void run() {
// We can't do anything without a RouterContext
if (_context == null)
return;
_thread = Thread.currentThread();
String specs = "";
while (_isRunning && _context.router().isAlive()) {
@ -103,7 +109,7 @@ public class StatSummarizer implements Runnable {
*/
List<Rate> parseSpecs(String specs) {
StringTokenizer tok = new StringTokenizer(specs, ",");
List<Rate> rv = new ArrayList<Rate>();
List<Rate> rv = new ArrayList<>();
while (tok.hasMoreTokens()) {
String spec = tok.nextToken();
int split = spec.lastIndexOf('.');
@ -111,7 +117,7 @@ public class StatSummarizer implements Runnable {
continue;
String name = spec.substring(0, split);
String per = spec.substring(split+1);
long period = -1;
long period;
try {
period = Long.parseLong(per);
RateStat rs = _context.statManager().getRate(name);

View File

@ -1,8 +1,17 @@
package net.i2p.android.router.stats;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import net.i2p.android.router.I2PActivityBase;
import net.i2p.android.router.R;
@ -10,20 +19,17 @@ import net.i2p.android.router.SettingsActivity;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener;
import net.i2p.stat.Rate;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.ActionBar;
import android.widget.ArrayAdapter;
import android.widget.SpinnerAdapter;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
public class RateGraphActivity extends I2PActivityBase {
private static final String SELECTED_RATE = "selected_rate";
private String[] mRates;
private long[] mPeriods;
private Spinner mSpinner;
private boolean mFinishOnResume;
@Override
@ -35,13 +41,13 @@ public class RateGraphActivity extends I2PActivityBase {
if (StatSummarizer.instance() != null) {
// Get the rates currently being graphed
List<SummaryListener> listeners = StatSummarizer.instance().getListeners();
TreeSet<SummaryListener> ordered = new TreeSet<SummaryListener>(new AlphaComparator());
TreeSet<SummaryListener> ordered = new TreeSet<>(new AlphaComparator());
ordered.addAll(listeners);
if (ordered.size() > 0) {
// Extract the rates and periods
final String[] mRates = new String[ordered.size()];
final long[] mPeriods = new long[ordered.size()];
mRates = new String[ordered.size()];
mPeriods = new long[ordered.size()];
int i = 0;
for (SummaryListener listener : ordered) {
Rate r = listener.getRate();
@ -50,35 +56,31 @@ public class RateGraphActivity extends I2PActivityBase {
i++;
}
// Set up action bar for drop-down list
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
mSpinner = (Spinner) findViewById(R.id.main_spinner);
mSpinner.setVisibility(View.VISIBLE);
SpinnerAdapter mSpinnerAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_dropdown_item, mRates);
mSpinner.setAdapter(new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, mRates));
ActionBar.OnNavigationListener mNavigationListener = new ActionBar.OnNavigationListener() {
String[] rates = mRates;
long[] periods = mPeriods;
public boolean onNavigationItemSelected(int position, long itemId) {
String rateName = rates[position];
long period = periods[position];
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f, rates[position]).commit();
return true;
mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selectRate(i);
}
};
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationListener);
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
if (savedInstanceState != null) {
int selected = savedInstanceState.getInt(SELECTED_RATE);
actionBar.setSelectedNavigationItem(selected);
}
mSpinner.setSelection(selected);
} else
selectRate(0);
} else {
DialogFragment df = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@ -112,6 +114,7 @@ public class RateGraphActivity extends I2PActivityBase {
}
} else {
DialogFragment df = new DialogFragment() {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@ -130,6 +133,14 @@ public class RateGraphActivity extends I2PActivityBase {
}
}
private void selectRate(int position) {
String rateName = mRates[position];
long period = mPeriods[position];
RateGraphFragment f = RateGraphFragment.newInstance(rateName, period);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_fragment, f, rateName).commit();
}
@Override
public void onResume() {
super.onResume();
@ -142,8 +153,8 @@ public class RateGraphActivity extends I2PActivityBase {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_RATE,
getSupportActionBar().getSelectedNavigationIndex());
if (mSpinner != null)
outState.putInt(SELECTED_RATE, mSpinner.getSelectedItemPosition());
}
private static class AlphaComparator implements Comparator<SummaryListener> {

View File

@ -1,11 +1,13 @@
package net.i2p.android.router.stats;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Observable;
import java.util.Observer;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.androidplot.Plot;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
@ -18,12 +20,13 @@ import net.i2p.android.router.R;
import net.i2p.android.router.service.StatSummarizer;
import net.i2p.android.router.service.SummaryListener;
import net.i2p.android.router.util.Util;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Observable;
import java.util.Observer;
public class RateGraphFragment extends I2PFragmentBase {
// redraws a plot whenever an update is received:
@ -131,8 +134,8 @@ public class RateGraphFragment extends I2PFragmentBase {
_ratePlot.setRangeValueFormat(new Format() {
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo,
@NonNull FieldPosition pos) {
double val = ((Number) obj).doubleValue();
if (val >= 10 * 1000 * 1000)
return new DecimalFormat("0 M").format(val / (1000 * 1000), toAppendTo, pos);
@ -147,7 +150,7 @@ public class RateGraphFragment extends I2PFragmentBase {
}
@Override
public Object parseObject(String source, ParsePosition pos) {
public Object parseObject(String source, @NonNull ParsePosition pos) {
return null;
}

View File

@ -4,6 +4,9 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import net.i2p.android.router.provider.CacheProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -14,7 +17,6 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.i2p.android.router.provider.CacheProvider;
/**
* A least recently used cache with a max number of entries
@ -32,7 +34,7 @@ public class AppCache {
/** the LRU cache */
private final Map<Integer, Object> _cache;
private static final Integer DUMMY = Integer.valueOf(0);
private static final Integer DUMMY = 0;
private static final String DIR_NAME = "appCache";
/** fragment into this many subdirectories */
private static final int NUM_DIRS = 32;
@ -89,7 +91,7 @@ public class AppCache {
public Uri addCacheFile(Uri key, boolean setAsCurrentBase) {
int hash = toHash(key);
synchronized(_cache) {
_cache.put(Integer.valueOf(hash), DUMMY);
_cache.put(hash, DUMMY);
}
// file:/// uri
//return Uri.fromFile(toFile(hash)).toString();
@ -104,7 +106,7 @@ public class AppCache {
public void removeCacheFile(Uri key) {
int hash = toHash(key);
synchronized(_cache) {
_cache.remove(Integer.valueOf(hash));
_cache.remove(hash);
}
deleteContent(key);
}
@ -121,7 +123,7 @@ public class AppCache {
// poke the LRU
Object present;
synchronized(_cache) {
present = _cache.get(Integer.valueOf(hash));
present = _cache.get(hash);
}
if (present != null)
setAsCurrentBase(key);
@ -173,17 +175,16 @@ public class AppCache {
File[] files = dir.listFiles();
if (files == null)
return 0;
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (f.isDirectory()) {
rv += enumerate(f, fileList);
} else {
long len = f.length();
if (len > 0) {
fileList.add(f);
rv += len;
for (File f : files) {
if (f.isDirectory()) {
rv += enumerate(f, fileList);
} else {
long len = f.length();
if (len > 0) {
fileList.add(f);
rv += len;
} else {
f.delete();
f.delete();
}
}
}
@ -195,7 +196,7 @@ public class AppCache {
try {
int hash = toHash(f);
synchronized(_cache) {
_cache.put(Integer.valueOf(hash), DUMMY);
_cache.put(hash, DUMMY);
}
} catch (IllegalArgumentException iae) {
Util.d("Huh bad file?" + iae);
@ -294,7 +295,7 @@ public class AppCache {
@Override
public Object put(Integer key, Object value) {
Object rv = super.put(key, value);
File f = toFile(key.intValue());
File f = toFile(key);
if (f.exists()) {
_totalSize += f.length();
}
@ -306,7 +307,7 @@ public class AppCache {
public Object remove(Object key) {
Object rv = super.remove(key);
if ( /* rv != null && */ key instanceof Integer) {
File f = toFile(((Integer)key).intValue());
File f = toFile((Integer) key);
if (f.exists()) {
_totalSize -= f.length();
f.delete();

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,134 @@
// License: MIT
// http://opensource.org/licenses/MIT
package net.i2p.android.router.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
/**
* Check device's network connectivity and speed.
*
* @author emil http://stackoverflow.com/users/220710/emil
* @author str4d
*/
public class Connectivity {
/**
* Get the network info.
*
* @param context the Context.
* @return the active NetworkInfo.
*/
public static NetworkInfo getNetworkInfo(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo();
}
/**
* Check if there is any connectivity at all.
*
* @param context the Context.
* @return true if we are connected to a network, false otherwise.
*/
public static boolean isConnected(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
// Works on emulator and devices.
// Note the use of isAvailable() - without this, isConnected() can
// return true when Wifi is disabled.
// http://stackoverflow.com/a/2937915
return (info != null && info.isAvailable() && info.isConnected());
}
/**
* Check if there is any connectivity to a Wifi network.
*
* @param context the Context.
* @return true if we are connected to a Wifi network, false otherwise.
*/
public static boolean isConnectedWifi(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
return (info != null && info.isAvailable() && info.isConnected() &&
info.getType() == ConnectivityManager.TYPE_WIFI);
}
/**
* Check if there is any connectivity to a mobile network.
*
* @param context the Context.
* @return true if we are connected to a mobile network, false otherwise.
*/
public static boolean isConnectedMobile(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
return (info != null && info.isAvailable() && info.isConnected() &&
info.getType() == ConnectivityManager.TYPE_MOBILE);
}
/**
* Check if there is fast connectivity.
*
* @param context the Context.
* @return true if we have "fast" connectivity, false otherwise.
*/
public static boolean isConnectedFast(Context context) {
NetworkInfo info = Connectivity.getNetworkInfo(context);
return (info != null && info.isAvailable() && info.isConnected() &&
Connectivity.isConnectionFast(info.getType(), info.getSubtype()));
}
/**
* Check if the connection is fast.
*
* @param type the network type.
* @param subType the network subtype.
* @return true if the provided type/subtype combination is classified as fast.
*/
public static boolean isConnectionFast(int type, int subType) {
if (type == ConnectivityManager.TYPE_WIFI) {
return true;
} else if (type == ConnectivityManager.TYPE_MOBILE) {
switch (subType) {
case TelephonyManager.NETWORK_TYPE_1xRTT:
return false; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_CDMA:
return false; // ~ 14-64 kbps
case TelephonyManager.NETWORK_TYPE_EDGE:
return false; // ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return true; // ~ 400-1000 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return true; // ~ 600-1400 kbps
case TelephonyManager.NETWORK_TYPE_GPRS:
return false; // ~ 100 kbps
case TelephonyManager.NETWORK_TYPE_HSDPA:
return true; // ~ 2-14 Mbps
case TelephonyManager.NETWORK_TYPE_HSPA:
return true; // ~ 700-1700 kbps
case TelephonyManager.NETWORK_TYPE_HSUPA:
return true; // ~ 1-23 Mbps
case TelephonyManager.NETWORK_TYPE_UMTS:
return true; // ~ 400-7000 kbps
/*
* Above API level 7, make sure to set android:targetSdkVersion
* to appropriate level to use these
*/
case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11
return true; // ~ 1-2 Mbps
case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9
return true; // ~ 5 Mbps
case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13
return true; // ~ 10-20 Mbps
case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8
return false; // ~25 kbps
case TelephonyManager.NETWORK_TYPE_LTE: // API level 11
return true; // ~ 10+ Mbps
// Unknown
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
default:
return false;
}
} else {
return false;
}
}
}

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

@ -1,9 +1,5 @@
package net.i2p.android.router.util;
import java.net.IDN;
import java.util.List;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
@ -18,6 +14,10 @@ import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.router.RouterContext;
import java.net.IDN;
import java.util.List;
import java.util.Locale;
public class NamingServiceUtil {
private static final String DEFAULT_NS = "BlockfileNamingService";
@ -106,7 +106,7 @@ public class NamingServiceUtil {
* Ref: java.net.IDN and RFC 3940
* @param host will be converted to lower case
* @return name converted to lower case and punycoded if necessary
* @throws IAE on various errors or if IDN is needed but not available
* @throws java.lang.IllegalArgumentException on various errors or if IDN is needed but not available
* @since 0.8.7
*/
@SuppressLint("NewApi")

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

@ -4,23 +4,31 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.content.res.Resources;
import android.preference.PreferenceManager;
import net.i2p.I2PAppContext;
import net.i2p.android.router.I2PConstants;
import net.i2p.data.DataHelper;
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.OrderedProperties;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public abstract class Util {
private static final boolean _isEmulator = Build.MODEL.equals("sdk");
public abstract class Util implements I2PConstants {
public static String getOurVersion(Context ctx) {
PackageManager pm = ctx.getPackageManager();
String us = ctx.getPackageName();
@ -36,18 +44,17 @@ public abstract class Util {
return "??";
}
public static boolean isConnected(Context ctx) {
// emulator always returns null NetworkInfo
if (_isEmulator)
return true;
NetworkInfo current = getNetworkInfo(ctx);
return current != null && current.isConnected();
}
public static NetworkInfo getNetworkInfo(Context ctx) {
ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo current = cm.getActiveNetworkInfo();
return current;
/**
* Get the active RouterContext.
*
* @return the active RouterContext, or null
*/
public static RouterContext getRouterContext() {
List<RouterContext> contexts = RouterContext.listContexts();
if ( !((contexts == null) || (contexts.isEmpty())) ) {
return contexts.get(0);
}
return null;
}
private static final String ANDROID_TAG = "I2P";
@ -119,44 +126,46 @@ public abstract class Util {
}
}
/** copied from various private components */
final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoport";
public static List<Properties> getPropertiesFromPreferences(Context context) {
List<Properties> pList = new ArrayList<Properties>();
List<Properties> pList = new ArrayList<>();
// Copy prefs
Properties routerProps = new OrderedProperties();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
// List to store stats for graphing
List<String> statSummaries = new ArrayList<String>();
List<String> statSummaries = new ArrayList<>();
// Properties to remove
Properties toRemove = new OrderedProperties();
// List to store Log settings
Properties logSettings = new OrderedProperties();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
Map<String, ?> all = preferences.getAll();
Iterator<String> iterator = all.keySet().iterator();
// get values from the Map and make them strings.
// This loop avoids needing to convert each one, or even know it's type, or if it exists yet.
while (iterator.hasNext()) {
String x = iterator.next();
if ( x.startsWith("i2pandroid.")) // Skip over UI-related I2P Android settings
continue;
else if ( x.startsWith("stat.summaries.")) {
for (String x : all.keySet()) {
if (x.startsWith("stat.summaries.")) {
String stat = x.substring("stat.summaries.".length());
String checked = all.get(x).toString();
if (checked.equals("true")) {
statSummaries.add(stat);
}
} else if ( x.startsWith("logger.")) {
} else if (x.startsWith("logger.")) {
logSettings.put(x, all.get(x).toString());
} else if (
x.equals("router.hiddenMode") ||
x.equals("i2cp.disableInterface")) {
x.equals("i2cp.disableInterface")) {
// special exception, we must invert the bool for these properties only.
String string = all.get(x).toString();
String inverted = Boolean.toString(!Boolean.parseBoolean(string));
routerProps.setProperty(x, inverted);
} else {
} else if (!x.startsWith(ANDROID_PREF_PREFIX)) { // Skip over UI-related I2P Android settings
String string = all.get(x).toString();
routerProps.setProperty(x, string);
}
@ -172,9 +181,161 @@ public abstract class Util {
routerProps.setProperty("stat.summaries", buf.toString());
}
// See net.i2p.router.web.ConfigNetHandler.saveChanges()
int udpPort = Integer.parseInt(routerProps.getProperty(UDPTransport.PROP_INTERNAL_PORT, "-1"));
if (udpPort <= 0)
routerProps.remove(UDPTransport.PROP_INTERNAL_PORT);
int ntcpPort = Integer.parseInt(routerProps.getProperty(PROP_I2NP_NTCP_PORT, "-1"));
boolean ntcpAutoPort = Boolean.parseBoolean(
routerProps.getProperty(PROP_I2NP_NTCP_AUTO_PORT, "true"));
if (ntcpPort <= 0 || ntcpAutoPort) {
routerProps.remove(PROP_I2NP_NTCP_PORT);
toRemove.setProperty(PROP_I2NP_NTCP_PORT, "");
}
pList.add(routerProps);
pList.add(toRemove);
pList.add(logSettings);
return pList;
}
// propName -> defaultValue
private static HashMap<String, Boolean> booleanOptionsRequiringRestart = new HashMap<>();
private static HashMap<String, String> stringOptionsRequiringRestart = new HashMap<>();
static {
HashMap<String, Boolean> boolToAdd = new HashMap<>();
HashMap<String, String> strToAdd = new HashMap<>();
boolToAdd.put(TransportManager.PROP_ENABLE_UPNP, true);
boolToAdd.put(TransportManager.PROP_ENABLE_NTCP, true);
boolToAdd.put(TransportManager.PROP_ENABLE_UDP, true);
boolToAdd.put(PROP_I2NP_NTCP_AUTO_PORT, true);
boolToAdd.put(Router.PROP_HIDDEN, false);
strToAdd.put(UDPTransport.PROP_INTERNAL_PORT, "-1");
strToAdd.put(PROP_I2NP_NTCP_PORT, "-1");
booleanOptionsRequiringRestart.putAll(boolToAdd);
stringOptionsRequiringRestart.putAll(strToAdd);
}
/**
* This function performs two tasks:
* <ul><li>
* The Properties object is modified to ensure that all options are valid
* for the current state of the Android device (e.g. what type of network
* the device is connected to).
* </li><li>
* The Properties object is checked to determine whether any options have
* changed that will require a router restart.
* </li></ul>
*
* @param props a Properties object containing the router.config
* @param toRemove a Collection of properties that will be removed
* @return true if the router needs to be restarted.
*/
public static boolean checkAndCorrectRouterConfig(Context context, Properties props, Collection<String> toRemove) {
// Disable UPnP on mobile networks, ignoring user's configuration
// TODO disabled until changes elsewhere are finished
//if (Connectivity.isConnectedMobile(context)) {
// props.setProperty(TransportManager.PROP_ENABLE_UPNP, Boolean.toString(false));
//}
// Now check if a restart is required
boolean restartRequired = false;
RouterContext rCtx = getRouterContext();
if (rCtx != null) {
for (Map.Entry<String, Boolean> option : booleanOptionsRequiringRestart.entrySet()) {
String propName = option.getKey();
boolean defaultValue = option.getValue();
boolean currentValue = defaultValue ? rCtx.getBooleanPropertyDefaultTrue(propName) : rCtx.getBooleanProperty(propName);
boolean newValue = Boolean.parseBoolean(props.getProperty(propName, Boolean.toString(defaultValue)));
restartRequired |= (currentValue != newValue);
}
if (!restartRequired) { // Cut out now if we already know the answer
for (Map.Entry<String, String> option : stringOptionsRequiringRestart.entrySet()) {
String propName = option.getKey();
String defaultValue = option.getValue();
String currentValue = rCtx.getProperty(propName, defaultValue);
String newValue = props.getProperty(propName, defaultValue);
restartRequired |= !currentValue.equals(newValue);
}
}
}
return restartRequired;
}
public static String getFileDir(Context context) {
// This needs to be changed so that we can have an alternative place
return context.getFilesDir().getAbsolutePath();
}
/**
* Write properties to a file. If the file does not exist, it is created.
* If the properties already exist in the file, they are updated.
*
* @param dir the file directory
* @param file relative to dir
* @param props properties to set
*/
public static void writePropertiesToFile(Context ctx, String dir, String file, Properties props) {
mergeResourceToFile(ctx, dir, file, 0, props, null);
}
/**
* Load defaults from resource, then add props from settings, and write back.
* If resID is 0, defaults are not written over the existing file content.
*
* @param dir the file directory
* @param file relative to dir
* @param resID the ID of the default resource, or 0
* @param userProps local properties or null
* @param toRemove properties to remove, or null
*/
public static void mergeResourceToFile(Context ctx, String dir, String file, int resID,
Properties userProps, Collection<String> toRemove) {
InputStream fin = null;
InputStream in = null;
try {
Properties props = new OrderedProperties();
try {
fin = new FileInputStream(new File(dir, file));
DataHelper.loadProps(props, fin);
if (resID > 0)
Util.d("Merging resource into file " + file);
else
Util.d("Merging properties into file " + file);
} catch (IOException ioe) {
if (resID > 0)
Util.d("Creating file " + file + " from resource");
else
Util.d("Creating file " + file + " from properties");
}
// write in default settings
if (resID > 0)
in = ctx.getResources().openRawResource(resID);
if (in != null)
DataHelper.loadProps(props, in);
// override with user settings
if (userProps != null)
props.putAll(userProps);
if (toRemove != null) {
for (String key : toRemove) {
props.remove(key);
}
}
File path = new File(dir, file);
DataHelper.storeProps(props, path);
Util.d("Saved " + props.size() +" properties in " + file);
} catch (IOException ioe) {
} catch (Resources.NotFoundException nfe) {
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
}
}
}

View File

@ -20,6 +20,7 @@ import java.io.OutputStream;
import net.i2p.android.apps.EepGetFetcher;
import net.i2p.android.router.provider.CacheProvider;
import net.i2p.android.router.util.AppCache;
import net.i2p.android.router.util.Connectivity;
import net.i2p.android.router.util.Util;
import net.i2p.data.DataHelper;
import net.i2p.util.EepGet;
@ -77,7 +78,7 @@ public class I2PWebViewClient extends WebViewClient {
return true;
}
if (!Util.isConnected(view.getContext())) {
if (!Connectivity.isConnected(view.getContext())) {
fail(view, "No Internet connection is available");
return true;
}
@ -193,8 +194,7 @@ public class I2PWebViewClient extends WebViewClient {
/**
* This should always be a content url
*/
void deleteCurrentPageCache(WebView view) {
String url = view.getUrl();
void deleteCurrentPageCache(WebView view, String url) {
Uri uri = Uri.parse(url);
if (CONTENT.equals(uri.getScheme())) {
try {

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

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

View File

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

View File

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

View File

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

View File

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

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.

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

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