Compare commits

...
125 Commits
Author SHA1 Message Date
Max Lv a3f57d2557 Revert dm7 scanner back to 1.9.3
The latest 1.9.5 version seems to have bugs on Android O.
2017-07-31 10:19:06 +08:00
Max Lv f5247d627d Bump version 2017-07-30 15:46:34 +08:00
Max Lv f19f8d8ffa Fix a crash when no profile selected 2017-07-30 15:32:48 +08:00
Mygod adaca76b12 Update plugin changes 2017-07-29 15:50:24 +08:00
Mygod 010fd6158f Fix profile name display in notification 2017-07-29 14:43:42 +08:00
Mygod 8b57b53788 Misc refinements 2017-07-29 14:36:51 +08:00
Mygod 1db2d81aca Update dependencies 2017-07-29 14:36:37 +08:00
Max Lv 258cb0c9c9 Enlarge the button's padding 2017-07-28 12:58:31 +08:00
Max Lv af057d0740 Add missing proguard rules #1300 2017-07-19 16:22:11 +08:00
Max Lv 52499f5c2d Add local DNS to dns list. #1281 2017-07-19 16:22:03 +08:00
Max Lv 230a018326 Allow adding URLs to custom rules. #1276 2017-07-19 16:21:53 +08:00
Max Lv f6f4f45f62 Add missing proguard rules #1300 2017-07-19 16:08:55 +08:00
Max Lv f950037003 Merge pull request #1258 from shadowsocks/o-preview
Support for Android O preview
2017-07-19 01:01:24 -07:00
Max Lv d8179b5b17 Fix a crash caused by evernote/android-job 2017-07-19 09:17:18 +08:00
Max Lv d701fb4cee Enlarge the text size of number picker 2017-07-19 09:16:56 +08:00
Max Lv 5549dfd20a Bump version 2017-07-16 16:04:53 +08:00
Max Lv 6978b921bb Fix a crash in RunnerActivity 2017-07-16 16:04:53 +08:00
Mygod ac28b83a0b Opt out of cleartext traffic 2017-07-16 15:52:55 +08:00
Mygod 422924f3f4 Opt-in for safe browsing 2017-07-16 15:36:20 +08:00
Mygod e1ee53567a Implement adaptive icons 2017-07-16 15:08:52 +08:00
Mygod 9c36f84b40 Update shortcut APIs 2017-07-15 02:10:00 +08:00
Mygod 143afb052d Use startService calls instead of AIDL to start services
Because our data store is process-safe, we can remove the `use` function
entirely and move more components to :bg process.
2017-07-15 01:52:53 +08:00
Mygod 27fc27346c Replace SharedPreferences with SQLite
Other changes included:

* Fix a bug where dirty flag is not set correctly when changing per-app
  proxy settings;
* Old auto connect settings in SharedPreference is now ignored.
2017-07-15 01:16:37 +08:00
Mygod 02c8be58e3 Remove obsolete strip in AndroidManifest 2017-07-15 01:01:34 +08:00
Mygod 2e1564b7b9 Remove usage of BuildCompat 2017-07-14 11:46:03 +08:00
Mygod 250373b92a Update Travis and README 2017-07-14 11:44:00 +08:00
Mygod dc439b0802 Update to API 26 2017-07-14 11:05:57 +08:00
Max Lv 26b6853061 Add local DNS to dns list. #1281 2017-06-18 15:59:47 +08:00
Max Lv a84070c2d3 Allow adding URLs to custom rules. #1276 2017-06-14 15:27:35 +08:00
Max Lv 49e30e7b09 Add back internal QR scanner. #1273 2017-06-05 09:28:34 +08:00
Max Lv 398db4f407 Add back internal QR scanner. #1273 2017-06-05 09:27:59 +08:00
Max Lv 8208ac1510 Bump version 2017-06-03 08:48:14 +08:00
Max Lv 5542f463a6 Roll back to Google's barcode scanner 2017-06-03 08:44:12 +08:00
Max Lv 1e750e45e9 Roll back to Google's barcode scanner 2017-05-29 17:46:42 +08:00
Max Lv 574f80aefd Fix a crash caused by new background limit on Android O 2017-05-26 18:01:07 +08:00
Mygod 424e1bd1bc Update to Android O preview 2
* Update support library to beta1;
* Adapt to new foreground service policy;
* Add a separate notification channel for NAT service;
* Adapt to `findViewById` method signature changes.

Possible issues: NAT notification will possibly not hide in lockscreen.
2017-05-18 14:56:26 +08:00
Mygod bbf52c4f1d Add support for notification channels for O preview 2017-05-18 14:35:29 +08:00
Mygod 0acfaa12c0 Use the new findViewById APIs 2017-05-18 14:35:29 +08:00
Mygod 1ec80175ea Update target SDK to O preview 2017-05-18 14:35:29 +08:00
Mygod 2b985686a4 Merge branch 'master' into develop 2017-05-18 14:35:18 +08:00
Mygod 095f4831db Remove duplicated files in Google Play services 2017-05-18 14:19:11 +08:00
Mygod ba75b7af75 Update Google Play services 2017-05-18 11:51:35 +08:00
Mygod d5dc3a8379 Change build toolchain for go to clang
Also adds helpful go-clean command.
2017-05-18 09:59:53 +08:00
Mygod 89ddc2f7c9 Use standard __ANDROID__ preprocessor macro 2017-05-18 09:59:42 +08:00
Mygod f721b57e00 Refine GuardedProcess to include stderr output 2017-05-18 09:59:29 +08:00
Mygod 3a785ab75e Fix Proguard warnings 2017-05-15 14:21:34 +08:00
Mygod b68689f731 Update okhttp 2017-05-15 14:05:18 +08:00
Max Lv cf2f17dfff Fix the protect socket issue on Android 4.4 2017-05-10 22:08:23 +08:00
Max Lv a64eb8ac58 Forward DNS packet to dnsgw 2017-05-10 22:08:23 +08:00
Mygod 9ab9230960 Merge strings from POEditor.com
Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)
2017-05-10 15:48:33 +08:00
Max Lv 8f6357d9cf Fix the dualstack issue on Android 4.4 2017-05-09 19:28:49 +08:00
Max Lv 73462b08af Remove debug info 2017-05-06 07:30:44 +08:00
Max Lv 6d07c03bc7 Fix #1186 2017-05-06 07:25:19 +08:00
Mygod 953f59ea43 Refine code style 2017-05-05 13:58:08 +08:00
Mygod 6df5179734 Remove redundant android-job proguard config 2017-05-05 13:53:04 +08:00
Mygod 2f312486a1 Avoid too many reschedules correctly 2017-05-05 13:43:19 +08:00
Mygod 88d5c01cb0 Update dependencies 2017-05-05 13:32:52 +08:00
Max Lv 2b05da3aff Bump version 2017-05-05 13:21:24 +08:00
Rallets c5a98a9b12 Limit abi for consistency (#1243) 2017-05-05 07:06:37 +08:00
Max Lv 5633ae7b21 Fix #1233 2017-04-29 18:51:54 +08:00
Max Lv 92af48f528 Fix #1236 2017-04-29 18:46:38 +08:00
Max Lv 59f02cfa53 Refine Firebase remote config 2017-04-28 16:50:41 +08:00
Max Lv bd782dae98 Avoid too many reschedules 2017-04-28 08:04:40 +08:00
Mygod ea738d17d7 Refine #1217 2017-04-24 11:51:47 +08:00
Mygod 915b1a74b1 Refine Firebase code 2017-04-24 11:37:47 +08:00
Mygod 9898ab88e0 Optimize firebase dependencies 2017-04-24 11:25:11 +08:00
Mygod c082a23c37 Update dependencies 2017-04-24 10:59:44 +08:00
Max Lv 6636cf815d Replace tag manager with firebase config 2017-04-23 23:11:44 +08:00
Max Lv 6df8f14fb2 Refine server name resolving 2017-04-23 21:49:01 +08:00
Mygod 6ca3e8abda Update scala to 2.11.11 2017-04-21 11:07:14 +08:00
Max Lv 0b88881fa4 Update shadowsocks-libev 2017-04-20 14:10:09 +08:00
V.E.O 85532a6b48 Add multiple remote dns support and remove alternative dns in global mode (#1217)
* Add multiple remote dns support. Remove alternative dns in global mode.
* Remove a redundant comment
2017-04-14 23:32:14 -05:00
Mygod 3a935b3274 Update scala to 2.11.10
It seems there will be 2.11.11 coming out soon however.

Source:
https://github.com/scala/scala-dev/issues/359#issuecomment-293474247
2017-04-14 00:14:45 +08:00
Mygod ea848f3b71 Merge branch 'master' into develop 2017-04-14 00:14:14 +08:00
Mygod 93274ffb04 Update android-job 2017-04-14 00:08:17 +08:00
Max Lv fcd6ed847a Bump version 2017-04-12 09:42:45 +08:00
Xingyu C 36c4d80ee4 MaxPermSize was removed in JDK 1.8 (#1207) 2017-04-12 09:33:48 +08:00
Mygod e864873ee4 Update issue_template.md to include remote DNS 2017-04-10 00:26:40 +08:00
Max Lv 80411c61d6 Add build files for overture 2017-04-09 10:10:04 +08:00
Max Lv adb7285d54 Remove overture-android submodule 2017-04-09 09:41:37 +08:00
Max Lv 7356eded16 Fix #1197 2017-04-09 09:40:19 +08:00
Mygod 1268376b68 Fix hostname not displayed correctly in notification 2017-04-07 19:47:06 +08:00
Mygod b71019149f Update issue template 2017-04-07 19:35:37 +08:00
Mygod 451e9c1fd3 Update dependencies 2017-04-07 19:33:09 +08:00
Max Lv 9d5c9456c9 Update overture dependencies 2017-04-07 11:12:17 +08:00
Max Lv 4988faf574 Fix #1196 2017-04-07 10:14:03 +08:00
Mygod 189058b5c4 Fix a typo caused by copy and paste 2017-04-06 20:37:32 +08:00
Mygod 529ee3a929 Remove unused misc_cat 2017-03-30 22:03:16 +08:00
Mygod 53af99eb98 Update support lib 2017-03-30 21:56:02 +08:00
Mygod 90cc3dbf2d Update dependencies 2017-03-30 21:54:15 +08:00
Mygod 6fe38f76be Update dependencies 2017-03-25 00:11:54 +08:00
Mygod 4df1d68501 Handle crashes caused by incorrect configured scanners
Fixes #1170.
2017-03-20 13:39:49 +08:00
Mygod 9eceefcceb Fix building 2017-03-17 19:05:32 +08:00
Max Lv af6e58ec46 Fix #1166 2017-03-17 10:34:13 +08:00
Mygod 9617418e70 Stop calling notifyChanged over aggressively in EditTextPreference 2017-03-15 19:24:18 +08:00
Mygod c09a96a203 Bump plugin lib version 2017-03-15 19:24:18 +08:00
Mygod 1ad08c6dba Update support lib version 2017-03-15 19:24:18 +08:00
Mygod 64c8529705 Update libsodium 2017-03-15 19:23:10 +08:00
Mygod b1670679d7 Add Korean 2017-03-15 18:55:03 +08:00
Mygod 5ac1343b5d Update strings.xml (POEditor.com) 2017-03-13 01:02:14 -05:00
Mygod 41f4f3a3bc Import strings.xml from POEditor.com
Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)

Update strings.xml (POEditor.com)
2017-03-13 13:58:51 +08:00
Max Lv c21bd74302 Merge branch 'beta' 2017-03-11 18:48:12 +08:00
Max Lv a8822330a3 Bump version 2017-03-11 18:43:03 +08:00
Max Lv 1cf6e4802c Update strings 2017-03-11 18:15:34 +08:00
Max Lv f64014eb7b Always forwording UDP traffic #1156 2017-03-11 15:49:06 +08:00
Mygod 2f6f991e6e Fix literal IPv6 address handling in URIs 2017-03-11 12:33:27 +08:00
Mygod 90e00a3857 Refine README.md 2017-03-11 12:11:52 +08:00
Mygod 712b764a9b Update README.md to reflect changes in sbt-android 2017-03-10 08:55:49 +08:00
Mygod 019f9d30e8 Remove redundant group in legacyPattern 2017-03-10 08:54:03 +08:00
Mygod 9c97dc9bf6 Update README.md to reflect changes in sbt-android 2017-03-09 23:54:48 +08:00
Mygod 7d2e0a515f Remove redundant group in legacyPattern 2017-03-09 20:46:34 +08:00
Mygod 26b68876f4 Update build instructions 2017-03-08 01:03:46 +08:00
Mygod a494f56ce8 Refine #1148 2017-03-08 00:25:05 +08:00
Max Lv a433822d0a Add arm64 support 2017-03-07 10:30:29 +08:00
Max Lv 60d7d90565 Fix a DNS issue with GFWList mode 2017-03-05 16:11:24 +08:00
Max Lv d2ba9493c0 Bump version 2017-03-04 10:26:43 +08:00
Max Lv 0eaece7af9 Remove external IP #1148 2017-03-04 09:56:52 +08:00
Max Lv c920c0daa4 Bump version 2017-02-24 19:37:23 +08:00
Mygod 8e3854b496 Update dependencies 2017-02-24 19:37:23 +08:00
Max Lv ad42167b2e Update shadowsocks-libev 2017-02-24 19:37:23 +08:00
Max Lv 3bf0963c5a Trim remoteDns 2017-02-24 19:37:23 +08:00
Max Lv c3315d35ed Fix #1130 2017-02-24 19:37:23 +08:00
Max Lv 103dc10a0a Update shadowsocks-libev 2017-02-24 19:37:23 +08:00
Max Lv 36c94b8d83 Fix a memory leak 2017-02-24 19:37:22 +08:00
Mygod df72b29302 Fix malformed URL in about.html 2017-02-23 15:17:57 +08:00
94 changed files with 1301 additions and 642 deletions
+2 -1
View File
@@ -29,7 +29,8 @@ _Put an `x` inside the [ ] that applies._
* [ ] IPv6 route
* [ ] Per-App Proxy
* [ ] Bypass mode
* [ ] UDP Forwarding
* Remote DNS: 8.8.8.8
* [ ] DNS Forwarding
* Plugin configuration (if applicable):
* [ ] Auto Connect
* [ ] TCP Fast Open
+12
View File
@@ -10,3 +10,15 @@ local.sbt
mobile/src/main/libs
mobile/src/main/obj
mobile/src/main/jni/overture
mobile/src/overture/.deps
mobile/src/overture/bin
mobile/src/overture/pkg
mobile/src/overture/src/github.com/Sirupsen
mobile/src/overture/src/github.com/mattn
mobile/src/overture/src/github.com/miekg
mobile/src/overture/src/github.com/shawn1m
mobile/src/overture/src/github.com/tools
mobile/src/overture/src/golang.org
mobile/src/overture/src/gopkg.in
+6 -3
View File
@@ -33,6 +33,9 @@
[submodule "mobile/src/main/jni/libudns"]
path = mobile/src/main/jni/libudns
url = https://github.com/shadowsocks/libudns.git
[submodule "mobile/src/overture"]
path = mobile/src/overture
url = https://github.com/shadowsocks/overture-android.git
[submodule "mobile/src/overture/go"]
path = mobile/src/overture/go
url = https://github.com/shadowsocks/go.git
[submodule "mobile/src/overture/src/github.com/shadowsocks/overture"]
path = mobile/src/overture/src/github.com/shadowsocks/overture
url = https://github.com/shadowsocks/overture.git
-1
View File
@@ -1,4 +1,3 @@
-XX:MaxPermSize=512m
-Xms1g
-Xmx3g
-Xss2m
+3 -3
View File
@@ -5,7 +5,7 @@ jdk:
env:
global:
- NDK_VERSION=r12b
- NDK_VERSION=r15b
- NDK_CCACHE=ccache
- GOROOT_BOOTSTRAP=$GOROOT
- ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION}
@@ -13,7 +13,7 @@ env:
- PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH}
scala:
- 2.11.8
- 2.11.11
before_cache:
- find $HOME/.sbt -name "*.lock" | xargs rm
@@ -27,7 +27,7 @@ cache:
android:
components:
- tools
- build-tools-25.0.2
- build-tools-26.0.0
- extra-android-m2repository
- extra-google-m2repository
+8 -6
View File
@@ -12,15 +12,17 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
* JDK 1.8
* SBT 0.13.0+
* Go 1.4+
* Android SDK
- Build Tools 25+
- Build Tools 26+
- Android Support Repository and Google Repository (see `build.sbt` for version)
* Android NDK r12b+
- Android NDK r15+
### BUILD
* Set environment variable `ANDROID_HOME` to `/path/to/android-sdk`
* Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk`
* (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`)
* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go`
* Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html
* Create `mobile/local.properties` from `mobile/local.properties.example` with your own key information
* Invoke the building like this
@@ -29,7 +31,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
git submodule update --init --recursive
# Build the App
sbt clean android:package-release
sbt clean go-build android:package-release
```
### TRANSLATE
@@ -45,13 +47,13 @@ Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) t
<ul>
<li>redsocks: <a href="https://github.com/shadowsocks/redsocks/blob/shadowsocks-android/README">APL 2.0</a></li>
<li>mbed TLS: <a href="https://github.com/ARMmbed/mbedtls/blob/development/LICENSE">APL 2.0</a></li>
<li>libevent: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libevent/LICENSE">BSD</a></li>
<li>libevent: <a href="https://github.com/shadowsocks/libevent/blob/master/LICENSE">BSD</a></li>
<li>tun2socks: <a href="https://github.com/shadowsocks/badvpn/blob/shadowsocks-android/COPYING">BSD</a></li>
<li>pcre: <a href="https://android.googlesource.com/platform/external/pcre/+/master/dist2/LICENCE">BSD</a></li>
<li>libancillary: <a href="https://github.com/shadowsocks/libancillary/blob/shadowsocks-android/COPYING">BSD</a></li>
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
<li>overture: <a href="https://github.com/shawn1m/overture/blob/master/LICENSE">MIT</a></li>
<li>libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/libev/LICENSE">GPLv2</a></li>
<li>libev: <a href="https://github.com/shadowsocks/libev/blob/master/LICENSE">GPLv2</a></li>
<li>libsodium: <a href="https://github.com/jedisct1/libsodium/blob/master/LICENSE">ISC</a></li>
<li>libudns: <a href="https://github.com/shadowsocks/libudns/blob/master/COPYING.LGPL">LGPL</a></li>
</ul>
+10 -5
View File
@@ -1,24 +1,29 @@
lazy val commonSettings = Seq(
scalaVersion := "2.11.8",
scalaVersion := "2.11.11",
dexMaxHeap := "4g",
organization := "com.github.shadowsocks",
platformTarget := "android-25",
platformTarget := "android-26",
compileOrder := CompileOrder.JavaThenScala,
javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil,
scalacOptions ++= "-target:jvm-1.7" :: "-Xexperimental" :: Nil,
ndkArgs := "-j" :: java.lang.Runtime.getRuntime.availableProcessors.toString :: Nil,
ndkAbiFilter := Seq("armeabi-v7a", "arm64-v8a", "x86"),
proguardVersion := "5.3.2",
proguardVersion := "5.3.3",
proguardCache := Seq(),
shrinkResources := true,
typedResources := false
typedResources := false,
resConfigs := Seq("ja", "ko", "ru", "zh-rCN", "zh-rTW"),
resolvers += "google" at "https://maven.google.com"
)
val supportLibsVersion = "25.2.0"
val supportLibsVersion = "26.0.0"
lazy val root = Project(id = "shadowsocks-android", base = file("."))
.settings(commonSettings)
.aggregate(plugin, mobile)
+37 -14
View File
@@ -1,43 +1,66 @@
enablePlugins(AndroidApp)
enablePlugins(AndroidGms)
android.useSupportVectors
name := "shadowsocks"
version := "4.1.0"
versionCode := Some(180)
version := "4.2.0"
versionCode := Some(190)
proguardOptions ++=
"-keep class com.github.shadowsocks.JniHelper { *; }" ::
"-dontwarn com.google.android.gms.internal.**" ::
"-dontwarn com.j256.ormlite.**" ::
"-dontwarn okhttp3.**" ::
"-dontwarn okio.**" ::
"-dontwarn org.xbill.**" ::
"-keep class com.github.shadowsocks.JniHelper { *; }" ::
"-dontwarn com.evernote.android.job.gcm.**" ::
"-dontwarn com.evernote.android.job.util.GcmAvailableHelper" ::
"-keep public class com.evernote.android.job.v21.PlatformJobService" ::
"-keep public class com.evernote.android.job.v14.PlatformAlarmService" ::
"-keep public class com.evernote.android.job.v14.PlatformAlarmReceiver" ::
"-keep public class com.evernote.android.job.JobBootReceiver" ::
"-keep public class com.evernote.android.job.JobRescheduleService" ::
Nil
resConfigs := Seq("ja", "ru", "zh-rCN", "zh-rTW")
val playServicesVersion = "10.2.0"
val playServicesVersion = "11.0.4"
resolvers += Resolver.jcenterRepo
libraryDependencies ++=
"com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" ::
"com.evernote" % "android-job" % "1.1.7" ::
"com.evernote" % "android-job" % "1.2.0-alpha3" ::
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
"com.google.android.gms" % "play-services-ads" % playServicesVersion ::
"com.google.android.gms" % "play-services-analytics" % playServicesVersion ::
"com.google.android.gms" % "play-services-gcm" % playServicesVersion ::
"com.google.firebase" % "firebase-config" % playServicesVersion ::
"com.j256.ormlite" % "ormlite-android" % "5.0" ::
"com.mikepenz" % "crossfader" % "1.5.0" ::
"com.mikepenz" % "fastadapter" % "2.1.7" ::
"com.mikepenz" % "iconics-core" % "2.8.2" ::
"com.mikepenz" % "materialdrawer" % "5.8.2" ::
"com.mikepenz" % "materialize" % "1.0.1" ::
"com.squareup.okhttp3" % "okhttp" % "3.6.0" ::
"com.mikepenz" % "fastadapter" % "2.6.2" ::
"com.mikepenz" % "iconics-core" % "2.8.9" ::
"com.mikepenz" % "materialdrawer" % "5.9.4" ::
"com.mikepenz" % "materialize" % "1.0.3" ::
"com.squareup.okhttp3" % "okhttp" % "3.8.1" ::
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" ::
"dnsjava" % "dnsjava" % "2.1.8" ::
"eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" ::
"eu.chainfire" % "libsuperuser" % "1.0.0.201704021214" ::
"me.dm7.barcodescanner" % "zxing" % "1.9.3" ::
"net.glxn.qrgen" % "android" % "2.0" ::
Nil
lazy val goBuild = TaskKey[Unit]("go-build", "Build go and kcptun")
packagingOptions := PackagingOptions(excludes =
"META-INF/maven/com.squareup.okio/okio/pom.properties" ::
"META-INF/maven/com.squareup.okio/okio/pom.xml" ::
"META-INF/maven/com.squareup.okhttp3/okhttp/pom.properties" ::
"META-INF/maven/com.squareup.okhttp3/okhttp/pom.xml" ::
Nil)
lazy val goClean = TaskKey[Unit]("go-clean", "Clean go build dependencies")
goClean := {
IO.delete(baseDirectory(base => base / "src/overture/.deps").value)
IO.delete(baseDirectory(base => base / "src/main/jni/overture").value)
}
lazy val goBuild = TaskKey[Unit]("go-build", "Build go and overture")
goBuild := {
Process(Seq("mobile/src/overture/make.bash", minSdkVersion.value)) ! streams.value.log match {
case 0 => // Success!
+50
View File
@@ -0,0 +1,50 @@
{
"project_info": {
"project_number": "261400168171",
"firebase_url": "https://admob-app-id-3330146721.firebaseio.com",
"project_id": "admob-app-id-3330146721",
"storage_bucket": "admob-app-id-3330146721.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:261400168171:android:dbdd6331c434162f",
"android_client_info": {
"package_name": "com.github.shadowsocks"
}
},
"oauth_client": [
{
"client_id": "261400168171-sfik8o3pj7e243583olorh7s5974vab1.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.github.shadowsocks",
"certificate_hash": "58a90f84cfe99d4280aec677c9a1292fae131677"
}
},
{
"client_id": "261400168171-g7aelv5bu012ojr7dod7lq09c9anjimh.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCee3fAad7nb3YsxeUO9mqqHFfAvsSCbVs"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}
+18 -12
View File
@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.touchscreen"
android:required="false"/>
@@ -11,16 +12,22 @@
android:required="false"/>
<uses-feature android:name="android.software.leanback"
android:required="false"/>
<uses-feature android:name="android.hardware.camera"
android:required="false"/>
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="25"/>
android:targetSdkVersion="26"/>
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true" />
<application
android:name=".ShadowsocksApplication"
android:icon="@mipmap/ic_launcher"
android:backupAgent=".ShadowsocksBackupAgent"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:banner="@drawable/ic_start_connected">
<meta-data android:name="com.google.android.gms.version"
@@ -75,6 +82,12 @@
android:excludeFromRecents="true"
android:launchMode="singleTask"/>
<activity
android:name=".ScannerActivity"
android:label="@string/add_profile_methods_scan_qr_code"
android:parentActivityName=".MainActivity"
android:excludeFromRecents="true"/>
<activity android:name=".TaskerActivity"
android:icon="@mipmap/ic_launcher">
<intent-filter>
@@ -87,18 +100,14 @@
android:label="@string/quick_toggle"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:taskAffinity=""
android:taskAffinity=""
android:process=":bg"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
</intent-filter>
</activity>
<service
android:name=".ShadowsocksRunnerService"
android:exported="false">
</service>
<service
android:name=".ShadowsocksNatService"
android:process=":bg"
@@ -125,13 +134,13 @@
</intent-filter>
</service>
<receiver android:name=".BootReceiver" android:enabled="false">
<receiver android:name=".BootReceiver" android:process=":bg" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".TaskerReceiver" tools:ignore="ExportedReceiver">
<receiver android:name=".TaskerReceiver" android:process=":bg" tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter>
@@ -150,9 +159,6 @@
android:process=":bg"/>
<receiver android:name="com.evernote.android.job.JobBootReceiver"
android:process=":bg"/>
<!-- Strip useless components -->
<activity android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity" tools:node="remove"/>
</application>
</manifest>
@@ -10,7 +10,4 @@ interface IShadowsocksService {
oneway void startListeningForBandwidth(IShadowsocksServiceCallback cb);
oneway void stopListeningForBandwidth(IShadowsocksServiceCallback cb);
oneway void unregisterCallback(IShadowsocksServiceCallback cb);
oneway void use(in int profileId);
void useSync(in int profileId);
}
+3 -3
View File
@@ -275,7 +275,7 @@ img {
<body>
<p>A <a href="http://shadowsocks.org">Shadowsocks</a> client for Android, written in Scala.</p>
<p>Copyright (C) 2017 by Max Lv <a href="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#x3a;&#x6d;&#97;x&#46;&#99;.&#108;&#118;&#x40;&#103;&#109;&#97;&#x69;&#108;&#46;&#x63;&#x6f;&#109;">&#x6d;&#97;&#x78;.&#x63;&#x2e;&#108;v&#64;&#x67;&#109;&#x61;&#x69;&#108;&#x2e;&#99;&#111;&#109;</a></p>
<p>Copyright (C) 2017 by Mygod Studio <a href="contact-shadowsocks-android@mygod.be">contact-shadowsocks-android@mygod.be</a></p>
<p>Copyright (C) 2017 by Mygod Studio <a href="mailto:contact-shadowsocks-android@mygod.be">contact-shadowsocks-android@mygod.be</a></p>
<p>This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
@@ -291,13 +291,13 @@ along with this program. If not, see <a href="http://www.gnu.org/licenses/">http
<ul>
<li>redsocks: <a href="https://github.com/shadowsocks/redsocks/blob/shadowsocks-android/README">APL 2.0</a></li>
<li>mbed TLS: <a href="https://github.com/ARMmbed/mbedtls/blob/development/LICENSE">APL 2.0</a></li>
<li>libevent: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libevent/LICENSE">BSD</a></li>
<li>libevent: <a href="https://github.com/shadowsocks/libevent/blob/master/LICENSE">BSD</a></li>
<li>tun2socks: <a href="https://github.com/shadowsocks/badvpn/blob/shadowsocks-android/COPYING">BSD</a></li>
<li>pcre: <a href="https://android.googlesource.com/platform/external/pcre/+/master/dist2/LICENCE">BSD</a></li>
<li>libancillary: <a href="https://github.com/shadowsocks/libancillary/blob/shadowsocks-android/COPYING">BSD</a></li>
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
<li>overture: <a href="https://github.com/shawn1m/overture/blob/master/LICENSE">MIT</a></li>
<li>libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/libev/LICENSE">GPLv2</a></li>
<li>libev: <a href="https://github.com/shadowsocks/libev/blob/master/LICENSE">GPLv2</a></li>
<li>libsodium: <a href="https://github.com/jedisct1/libsodium/blob/master/LICENSE">ISC</a></li>
<li>libudns: <a href="https://github.com/shadowsocks/libudns/blob/master/COPYING.LGPL">LGPL</a></li>
</ul>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 41 KiB

@@ -0,0 +1,64 @@
/*
* Copyright 2010 Brave New Software Project, Inc.
*
* 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 com.github.shadowsocks.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.os.Build;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
public class Dns {
public static String getDnsResolver(Context context) throws Exception {
Collection<InetAddress> dnsResolvers = getDnsResolvers(context);
if (dnsResolvers.isEmpty()) {
throw new Exception("Couldn't find an active DNS resolver");
}
String dnsResolver = dnsResolvers.iterator().next().toString();
if (dnsResolver.startsWith("/")) {
dnsResolver = dnsResolver.substring(1);
}
return dnsResolver;
}
private static Collection<InetAddress> getDnsResolvers(Context context) throws Exception {
ArrayList<InetAddress> addresses = new ArrayList<InetAddress>();
ConnectivityManager connectivityManager =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties");
Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
if (linkProperties != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses");
Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
for (Object dns : dnses) {
addresses.add((InetAddress)dns);
}
} else {
addresses.addAll(((LinkProperties) linkProperties).getDnsServers());
}
}
return addresses;
}
}
+13 -11
View File
@@ -26,10 +26,12 @@ include $(CLEAR_VARS)
SODIUM_SOURCE := \
crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \
crypto_core/salsa20/ref/core_salsa20.c \
crypto_generichash/blake2/ref/blake2b-compress-ref.c \
crypto_generichash/blake2/ref/blake2b-ref.c \
crypto_generichash/blake2/ref/generichash_blake2b.c \
crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \
crypto_core/hchacha20/core_hchacha20.c \
crypto_core/salsa/ref/core_salsa_ref.c \
crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
crypto_generichash/blake2b/ref/blake2b-ref.c \
crypto_generichash/blake2b/ref/generichash_blake2b.c \
crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
crypto_pwhash/crypto_pwhash.c \
@@ -41,10 +43,10 @@ SODIUM_SOURCE := \
crypto_pwhash/argon2/pwhash_argon2i.c \
crypto_scalarmult/curve25519/scalarmult_curve25519.c \
crypto_stream/chacha20/stream_chacha20.c \
crypto_stream/chacha20/ref/stream_chacha20_ref.c \
crypto_stream/salsa20/ref/stream_salsa20_ref.c \
crypto_stream/salsa20/ref/xor_salsa20_ref.c \
crypto_verify/16/ref/verify_16.c \
crypto_stream/chacha20/ref/chacha20_ref.c \
crypto_stream/salsa20/stream_salsa20.c \
crypto_stream/salsa20/ref/salsa20_ref.c \
crypto_verify/sodium/verify.c \
randombytes/randombytes.c \
randombytes/sysrandom/randombytes_sysrandom.c \
sodium/core.c \
@@ -97,7 +99,7 @@ LIBEVENT_SOURCES := \
LOCAL_MODULE := event
LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES))
LOCAL_CFLAGS := -O2 -I$(LOCAL_PATH)/libevent \
LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \
-I$(LOCAL_PATH)/libevent/include \
include $(BUILD_STATIC_LIBRARY)
@@ -255,7 +257,7 @@ SHADOWSOCKS_SOURCES := local.c \
LOCAL_MODULE := ss-local
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \
-DUSE_CRYPTO_MBEDTLS -DANDROID -DHAVE_CONFIG_H \
-DUSE_CRYPTO_MBEDTLS -DHAVE_CONFIG_H \
-DCONNECT_IN_PROGRESS=EINPROGRESS \
-I$(LOCAL_PATH)/include/shadowsocks-libev \
-I$(LOCAL_PATH)/include \
@@ -292,7 +294,7 @@ SHADOWSOCKS_SOURCES := tunnel.c \
LOCAL_MODULE := ss-tunnel
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \
-DUSE_CRYPTO_MBEDTLS -DANDROID -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
-DUSE_CRYPTO_MBEDTLS -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
-DCONNECT_IN_PROGRESS=EINPROGRESS \
-I$(LOCAL_PATH)/libancillary \
-I$(LOCAL_PATH)/include \
+1 -1
View File
@@ -1,4 +1,4 @@
APP_ABI := armeabi-v7a x86
APP_ABI := armeabi-v7a arm64-v8a x86
APP_PLATFORM := android-19
APP_STL := stlport_static
NDK_TOOLCHAIN_VERSION := clang
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:startColor="#19000000"
android:endColor="#00000000"
android:startX="46.15"
android:endX="108"
android:startY="40.34"
android:endY="102.18"
android:type="linear"/>
@@ -0,0 +1,13 @@
<vector android:height="108dp" android:viewportHeight="108.0"
android:viewportWidth="108.0" android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/ic_launcher_foreground_shadow"
android:pathData="M70.94,35.7l37.06,36.3l0,36l-23.92,0l-53.02,-52.57l39.88,-19.73l0,0"/>
<path android:fillAlpha="0.79" android:fillColor="#fff"
android:pathData="M70.94,35.7l-6.94,30.53l-15.56,-5l13.74,-16.38l-18.31,15.4l-12.81,-4.82l39.88,-19.73z"/>
<path android:fillAlpha="0.79" android:fillColor="#fff"
android:pathData="M48.79,64.37l4.96,1.6l-4.94,6.73l-0.02,-8.33z"/>
<path android:fillAlpha="0.79" android:fillColor="#fff"
android:pathData="M70.94,35.7l-6.94,30.53l-15.56,-5l13.74,-16.38l-18.31,15.4l-12.81,-4.82l39.88,-19.73z"/>
<path android:fillAlpha="0.79" android:fillColor="#fff"
android:pathData="M48.79,64.37l4.96,1.6l-4.94,6.73l-0.02,-8.33z"/>
</vector>
@@ -37,7 +37,7 @@
<android.support.v7.widget.AppCompatImageView android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:padding="8dp"
android:layout_gravity="top"
app:srcCompat="@drawable/ic_social_share"
android:background="?attr/selectableItemBackgroundBorderless"
@@ -47,7 +47,7 @@
<android.support.v7.widget.AppCompatImageView android:id="@+id/edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:padding="8dp"
android:layout_gravity="top"
app:srcCompat="@drawable/ic_image_edit"
android:background="?attr/selectableItemBackgroundBorderless"
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar_light_dark" />
<me.dm7.barcodescanner.zxing.ZXingScannerView android:id="@+id/scanner"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
+39 -44
View File
@@ -7,7 +7,7 @@
<string name="profile">"プロファイル"</string>
<string name="profile_summary">"新規作成または既存ファイルを開く"</string>
<string name="nat">"NAT モード (デバックのみ)"</string>
<string name="nat_summary">"VPNモードからNATモードに切り替えるにはROOT権限が必要です"</string>
<string name="nat_summary">"VPN モードから NAT モードに切り替えるには ROOT 権限が必要です"</string>
<string name="remote_dns">"リモートDNS"</string>
<string name="stat_summary">"送信済み: \t%3$s\t↑\t%1$s/s
受信済み: \t%4$s\t↓\t%2$s/s"</string>
@@ -19,7 +19,6 @@
<string name="connection_test_error_status_code">"ステータスコード無効(#%d)"</string>
<!-- proxy category -->
<string name="proxy_cat">"サーバー設定"</string>
<string name="profile_name">"サーバー名"</string>
<string name="proxy">"サーバーアドレス"</string>
<string name="remote_port">"リモートポート"</string>
@@ -28,11 +27,8 @@
<string name="enc_method">"暗号化方式"</string>
<!-- feature category -->
<string name="feature_cat">"ファンクション設定"</string>
<string name="ipv6">"IPv6プロキシ"</string>
<string name="ipv6_summary">"リモートサーバーにIPv6パケットを転送"</string>
<string name="onetime_auth">"ワンタイム認証"</string>
<string name="onetime_auth_summary">"ワンタイム認証を使用する"</string>
<string name="ipv6">"IPv6 プロキシ"</string>
<string name="ipv6_summary">"リモートサーバーに IPv6 パケットを転送"</string>
<string name="route_list">"プロキシ方式"</string>
<string name="route_entry_gfwlist">"中国本土からアクセス不可なアドレス以外を迂回する"</string>
<string name="proxied_apps">"アプリ別のプロキシを使用"</string>
@@ -42,28 +38,25 @@
<string name="bypass_apps_summary">"このモードを選択した場合、指定したアプリのパケットはプロキシを経由しない"</string>
<string name="auto_connect">"自動接続"</string>
<string name="auto_connect_summary">"システム起動時にバックグラウンドで本サービスを開始"</string>
<string name="tcp_fastopen_summary">"使用するにはROOT権限が必要"</string>
<string name="tcp_fastopen_summary">"使用するには ROOT 権限が必要"</string>
<string name="tcp_fastopen_summary_unsupported">"ご利用のカーネルバージョンはサポートしておりません:%s &lt; 3.7.1"</string>
<string name="udp_dns">"UDP転送"</string>
<string name="udp_dns_summary">"UDPプロトコルでリモードサーバーにパケットを転送"</string>
<string name="udp_dns">"DNS 転送"</string>
<string name="udp_dns_summary">"DNS プロトコルでリモードサーバーにパケットを転送"</string>
<!-- notification category -->
<string name="forward_success">"バックグラウンドでサービスを開始しました"</string>
<string name="invalid_server">"サーバーが無効です"</string>
<string name="service_failed">"リモートサーバーに接続できません"</string>
<string name="method_unsafe">"注意:現在の暗号化方式は安全ではありません"</string>
<string name="nat_deprecated">"注意:Android 5.0及びそれ以降のバージョンではNATモードの使用はお勧めしません"</string>
<string name="nat_no_root">"NATモードはROOT権限が必要です"</string>
<string name="switch_to_vpn">"VPNモードに切り替える"</string>
<string name="nat_deprecated">"注意:Android 5.0 及びそれ以降のバージョンでは NAT モードの使用はお勧めしません"</string>
<string name="nat_no_root">"NAT モードは ROOT 権限が必要です"</string>
<string name="switch_to_vpn">"VPN モードに切り替える"</string>
<string name="stop">"中止"</string>
<string name="stopping">"停止中……"</string>
<string name="vpn_error">"バックグランドサービスの起動に失敗しました:%s"</string>
<string name="reboot_required">"VPNサービスの起動に失敗しました、デバイスの再起動を試みて下さい"</string>
<string name="profile_invalid_input">"有効なデータが見つかりません"</string>
<string name="reboot_required">"VPN サービスの起動に失敗しました、デバイスの再起動を試みて下さい"</string>
<string name="profile_invalid_input">"有効なプロファイルが見つかりません"</string>
<!-- alert category -->
<string name="yes">"はい"</string>
<string name="no">"いいえ"</string>
<string name="close">"閉じる"</string>
<string name="profile_empty">"プロファイルを選択して下さい"</string>
<string name="proxy_empty">"サーバアドレスやパスワードの入力が必要です"</string>
@@ -74,9 +67,10 @@
<!-- menu category -->
<string name="profiles">"プロファイル"</string>
<string name="settings">"オプション設定"</string>
<string name="faq">"質問"</string>
<string name="faq">"よくある質問"</string>
<string name="recovery">"リセット"</string>
<string name="about">"本アプリについて"</string>
<string name="about_title">"Shadowsocks %s"</string>
<string name="edit">"編集"</string>
<string name="share">"共有"</string>
<string name="add_profile">"プロファイルを追加"</string>
@@ -85,24 +79,23 @@
<string name="action_import">"クリップボードからインポート"</string>
<string name="action_export_msg">"クリップボードの出力に成功しました"</string>
<string name="action_export_err">"クリップボードの出力に失敗しました"</string>
<string name="action_import_msg">"インポートは成功しました"</string>
<string name="action_import_err">"インポートは失敗しました"</string>
<string name="action_import_msg">"インポートに成功しました"</string>
<string name="action_import_err">"インポートに失敗しました"</string>
<!-- profile -->
<string name="profile_config">"プロファイル編集"</string>
<string name="unsaved_changes_prompt">"編集は保存されておりません、保存しますか?"</string>
<string name="apply">"適応する"</string>
<string name="delete">"削除する"</string>
<string name="delete_confirm_prompt">"このプロファイルを削除しますか?"</string>
<string name="share_qr_nfc">"QRコード/NFC"</string>
<string name="add_profile_dialog">"シャドーソックスにプロファイルを追加しますか"</string>
<string name="add_profile_methods_scan_qr_code">"QRコードを読み取る"</string>
<string name="add_profile_methods_manual_settings">"手動でセットアップ"</string>
<string name="delete">"削除"</string>
<string name="delete_confirm_prompt">"このプロファイルを削除しますか"</string>
<string name="share_qr_nfc">"QR コード / NFC"</string>
<string name="add_profile_dialog">"Shadowsocks用プロファイルを追加しますか"</string>
<string name="add_profile_methods_scan_qr_code">"QR コードを読み取る"</string>
<string name="add_profile_methods_manual_settings">"手動設定"</string>
<string name="add_profile_scanner_not_installed">"Zxingに準拠したQRコードスキャンアプリをインストールして下さい"</string>
<string name="add_profile_scanner_permission_required">QRコードを読み取るにはカメラの使用権限が必要です。</string>
<plurals name="removed">
<item quantity="other">"削除済み"</item>
</plurals>
<string name="undo">"撤回"</string>
<string name="undo">"元に戻す"</string>
<!-- tasker -->
<string name="toggle_service_state">"サービスを開始"</string>
@@ -114,28 +107,30 @@
<!-- status -->
<string name="sent">"送信済み:"</string>
<string name="received">"受信済み:"</string>
<string name="connecting">"接続中..."</string>
<string name="connecting">"接続中…"</string>
<string name="vpn_connected">"接続済み、タップして接続状況をチェック"</string>
<string name="nat_connected">"接続済み"</string>
<string name="not_connected">"接続されませんでした"</string>
<!-- kcp -->
<string name="kcp_summary">"KCP プロトコル を有効にする"</string>
<string name="kcp_port">"KCP ポート"</string>
<string name="kcp_port_summary">"%d(リモート KCPTUN サーバーのポート番号)"</string>
<string name="kcpcli">"KCP パラメータ"</string>
<string name="not_connected">"未接続"</string>
<!-- acl -->
<string name="custom_rules">"カスタムルール"</string>
<string name="action_selection">"選択"</string>
<string name="action_add_rule">"ルールを追加"</string>
<string name="action_add_rule">"ルールを追加…"</string>
<string name="edit_rule">"ルールを編集"</string>
<string name="route_entry_all">"すべて"</string>
<string name="route_entry_bypass_lan">"LANアドレスを迂回する"</string>
<string name="route_entry_bypass_lan">"LAN アドレスを迂回する"</string>
<string name="route_entry_bypass_chn">"中国本土のアドレスを迂回する"</string>
<string name="route_entry_bypass_lan_chn">"LAN及び中国本土のアドレスを迂回する"</string>
<string name="route_entry_bypass_lan_chn">"LAN 及び中国本土のアドレスを迂回する"</string>
<string name="route_entry_chinalist">"中国本土のアドレス以外を迂回する"</string>
<string name="proxied_apps_summary_v21">"選択したアプリにプロキシを設定する"</string>
<string name="acl_rule_templates_generic">"サブネット/ホストネームPCREパターン"</string>
<string name="acl_rule_templates_domain">"ドメイン及びサブドメイン"</string>
</resources>
<string name="acl_rule_templates_generic">"URL/サブネット/ホスト名 PCRE パターン"</string>
<string name="acl_rule_templates_domain">"ドメイン及び全てのサブドメイン"</string>
<!-- plugin -->
<string name="plugin">"プラグイン"</string>
<string name="plugin_configure">"設定..."</string>
<string name="plugin_disabled">"無効"</string>
<string name="plugin_unknown">"不明なプラグイン"</string>
<string name="plugin_untrusted">"警告:このプラグインは信頼されていないソースからの可能性があります"</string>
<string name="profile_plugin">"プラグイン"</string>
</resources>
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="quick_toggle">"켜기/끄기"</string>
<!-- misc -->
<string name="profile">"프로필"</string>
<string name="profile_summary">"다른 프로필로 전환하거나 새 프로필 추가"</string>
<string name="nat">"NAT 모드 (지원 중단)"</string>
<string name="nat_summary">"VPN 모드 대신 NAT 모드를 사용합니다. 루트 권한이 필요합니다."</string>
<string name="remote_dns">"원격 DNS"</string>
<string name="stat_summary">"송신: \t\t\t\t\t%3$s\t↑\t%1$s/s
수신: \t%4$s\t↓\t%2$s/s "</string>
<string name="stat_profiles">"%1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"%1$s/s↑\t%2$s/s↓"</string>
<plurals name="bytes">
<item quantity="other">"바이트"</item>
</plurals>
<string name="connection_test_pending">"인터넷 연결 상태 검사하기"</string>
<string name="connection_test_testing">"검사 중…"</string>
<string name="connection_test_available">"성공: %dms"</string>
<string name="connection_test_error">"인터넷 연결을 찾을 수 없습니다: %s"</string>
<string name="connection_test_fail">"인터넷에 연결할 수 없습니다"</string>
<string name="connection_test_error_status_code">"오류 코드: #%d"</string>
<!-- proxy category -->
<string name="profile_name">"프로필 이름"</string>
<string name="proxy">"서버 주소"</string>
<string name="remote_port">"원격 포트"</string>
<string name="port">"로컬 포트"</string>
<string name="sitekey">"비밀번호"</string>
<string name="enc_method">"암호화 방법"</string>
<!-- feature category -->
<string name="ipv6">"IPv6 라우팅"</string>
<string name="ipv6_summary">"IPv6 트래픽도 원격으로 리다이렉트 합니다"</string>
<string name="route_list">"라우팅 대상"</string>
<string name="proxied_apps">"원하는 앱만 프락시 적용하기"</string>
<string name="proxied_apps_summary">"선택한 앱에만 프락시를 적용합니다. Android 4.x 이하에서는 NAT 모드를 써야 합니다."</string>
<string name="on">"활성화"</string>
<string name="bypass_apps">"선택된 앱들만 프록시 적용 제외하기"</string>
<string name="bypass_apps_summary">"이 옵션을 활성화하면 선택된 앱들은 프락시를 사용하지 않습니다"</string>
<string name="auto_connect">"자동 접속"</string>
<string name="auto_connect_summary">"장치가 켜질 때 Shadowsocks를 자동으로 활성화합니다"</string>
<string name="tcp_fastopen_summary">"루트 권한이 필요합니다"</string>
<string name="tcp_fastopen_summary_unsupported">"지원하지 않는 버전의 커널입니다: %s &lt; 3.7.1"</string>
<string name="udp_dns">"DNS 포워딩"</string>
<string name="udp_dns_summary">"모든 DNS 요청을 외부로 포워딩 합니다"</string>
</resources>
+62 -42
View File
@@ -2,26 +2,31 @@
<resources>
<string name="quick_toggle">"Подключение"</string>
<!-- global/misc category -->
<string name="misc_cat">"Разное"</string>
<!-- misc -->
<string name="profile">"Профиль"</string>
<string name="profile_summary">"Переключить на другой профиль или добавить новые"</string>
<string name="nat">"Режим NAT (не рекомендуется)"</string>
<string name="nat_summary">"Использовать режим NAT вместо VPN. Требует ROOT прав."</string>
<string name="stat_summary">"Отправлено: \t\t\t\t\t%3$s\t↑\t%1$s/s
<string name="remote_dns">"Удалённый DNS"</string>
<string name="stat_summary">"Отправлено: \t%3$s\t↑\t%1$s/s
Получено: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_profiles">"
%1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"%1$s/s↑\t%2$s/s↓"</string>
<string name="connection_test_pending">"Проверить подключение"</string>
<plurals name="bytes">
<item quantity="one">"байт"</item>
<item quantity="few">"байта"</item>
<item quantity="many">"байт"</item>
<item quantity="other">"байт"</item>
</plurals>
<string name="connection_test_pending">"Проверить соединение"</string>
<string name="connection_test_testing">"Проверка…"</string>
<string name="connection_test_available">"Успешно: %dms задержка"</string>
<string name="connection_test_error">"Сбой проверки интернет подключения: %s"</string>
<string name="connection_test_fail">"Интернет Недоступен"</string>
<string name="connection_test_available">"Успешно: задержка %d мс"</string>
<string name="connection_test_error">"Сбой проверки интернет-соединения: %s"</string>
<string name="connection_test_fail">"Интернет недоступен"</string>
<string name="connection_test_error_status_code">"Код ошибки: #%d"</string>
<!-- proxy category -->
<string name="proxy_cat">"Настройки Сервера"</string>
<string name="profile_name">"Имя Профиля"</string>
<string name="proxy">"Сервер"</string>
<string name="remote_port">"Удалённый порт"</string>
@@ -30,17 +35,8 @@
<string name="enc_method">"Метод Шифрования"</string>
<!-- feature category -->
<string name="feature_cat">"Дополнительные Настройки"</string>
<string name="ipv6">"IPv6 Маршрут"</string>
<string name="ipv6_summary">"Перенаправлять IPv6 траффик на удалённый сервер"</string>
<string name="onetime_auth">"Единоразовая Аутентификация"</string>
<string name="onetime_auth_summary">"Включить единоразовую аутентификацию"</string>
<string name="http_proxy">"HTTP Прокси"</string>
<string name="http_proxy_summary">"Локальный HTTP прокси"</string>
<string name="dns_proxy">"DNS Прокси"</string>
<string name="dns_proxy_summary">"Перенаправлять DNS запросы на удалённый сервер"</string>
<string name="auto_set_gfwlist">"CHN Маршрут"</string>
<string name="auto_set_gfwlist_summary">"Игнорировать все сайты находящиеся в Китае"</string>
<string name="ipv6_summary">"Перенаправлять трафик IPv6 на удалённый сервер"</string>
<string name="route_list">"Маршрут"</string>
<string name="route_entry_gfwlist">"Список GFW"</string>
<string name="proxied_apps">"Прокси для выбранных приложений"</string>
@@ -52,27 +48,26 @@
<string name="auto_connect_summary">"Запускать Shadowsocks при включении"</string>
<string name="tcp_fastopen_summary">"Переключение требует наличия ROOT прав"</string>
<string name="tcp_fastopen_summary_unsupported">"Неподдерживаемая версия ядра: %s &lt; 3.7.1"</string>
<string name="udp_dns">"Проброс UDP"</string>
<string name="udp_dns_summary">"Пробрасывать UDP пакеты на удалённый сервер"</string>
<string name="udp_dns">"Перенаправление DNS"</string>
<string name="udp_dns_summary">"Перенаправлять все DNS-запросы на удалённый сервер"</string>
<!-- notification category -->
<string name="forward_success">"Shadowsocks запущен."</string>
<string name="invalid_server">"Неправильное имя сервера"</string>
<string name="service_failed">"Ошибка при подключении к удалённому серверу"</string>
<string name="method_unsafe">"ВНИМАНИЕ:В настоящее время метод шифрования является небезопасным"</string>
<string name="nat_deprecated">"ВНИМАНИЕ:режим NAT не работает в Android 5.0"</string>
<string name="nat_deprecated">"ВНИМАНИЕ: режим NAT не рекомендуется, начиная с Android 5.0"</string>
<string name="nat_no_root">"Режим NAT требует наличия ROOT прав"</string>
<string name="switch_to_vpn">"Переключить на режим VPN"</string>
<string name="stop">"Остановить"</string>
<string name="stopping">"Останавливается…"</string>
<string name="vpn_error">"%s"</string>
<string name="reboot_required">"Сбой запуска VPN сервиса. Требуется перезагрузка Вашего устройства."</string>
<string name="reboot_required">"Не удалось запустить службу VPN. Возможно, требуется перезагрузить ваше устройство."</string>
<!-- alert category -->
<string name="close">"Закрыть"</string>
<string name="profile_empty">"Пожалуйста, выберите профиль"</string>
<string name="proxy_empty">"Прокси/Пароль не должны быть пустыми"</string>
<string name="connect">"Подключить"</string>
<string name="initializing">"Инициализация…"</string>
<string name="recovering">"Сброс…"</string>
<string name="loading">"Загрузка…"</string>
<string name="remove_profile">"Удалить этот профиль %s?"</string>
<!-- menu category -->
@@ -81,11 +76,8 @@
<string name="recovery">"Сброс"</string>
<string name="about">"О приложении"</string>
<string name="about_title">"Shadowsocks %s"</string>
<string name="edit">"Изменить"</string>
<string name="share">"Поделиться"</string>
<string name="flush_dnscache">"Очистить кэш DNS"</string>
<string name="flush_dnscache_summary">"Работает только в режиме NAT. Требует наличия ROOT прав."</string>
<string name="flush_dnscache_no_root">"Очистка кэша DNS не работает без налиичя ROOT прав"</string>
<string name="flushing">"Очистка"</string>
<string name="add_profile">"Добавить Профиль"</string>
<string name="action_apply_all">"Применить настройки для всех профилей"</string>
<string name="action_export">"Экспортировать в буфер обмена"</string>
@@ -94,13 +86,27 @@
<string name="action_export_err">"Сбой экспорта."</string>
<string name="action_import_msg">"Успешный импорт!"</string>
<string name="action_import_err">"Сбой импорта."</string>
<!-- profile -->
<string name="profile_config">"Настройка профиля"</string>
<string name="delete">"Удалить"</string>
<string name="delete_confirm_prompt">"Вы уверены, что хотите удалить этот профиль?"</string>
<string name="share_qr_nfc">"QR-код/NFC"</string>
<string name="add_profile_dialog">"Добавить этот Профиль Shadowsocks?"</string>
<string name="add_profile_methods_scan_qr_code">"Сканировать QR код"</string>
<string name="add_profile_methods_manual_settings">"Настройка вручную"</string>
<string name="add_profile_methods_scan_qr_code">"Сканировать QR-код"</string>
<string name="add_profile_methods_manual_settings">"Ручные настройки"</string>
<string name="add_profile_scanner_not_installed">"Пожалуйста, установите любое ZXing-совместимое приложение для сканирования QR-кодов."</string>
<string name="add_profile_scanner_permission_required">Разрешение камеры требуется для сканирования QR код.</string>
<plurals name="removed">
<item quantity="one">"Удалено"</item>
<item quantity="few">"Удалено %d элемента"</item>
<item quantity="many">"Удалено %d элементов"</item>
<item quantity="other">"Удалено %d элементов"</item>
</plurals>
<string name="undo">"Вернуть"</string>
<!-- tasker -->
<string name="toggle_service_state">"Запустить сервисStart the service"</string>
<string name="toggle_service_state">"Запустить службу"</string>
<string name="start_service_default">"Подключиться к текущему серверу"</string>
<string name="start_service">"Подключиться к %s"</string>
<string name="stop_service">"Переключить на %s"</string>
@@ -109,16 +115,30 @@
<!-- status -->
<string name="sent">"Отправлено:"</string>
<string name="received">"Получено:"</string>
<string name="connecting">"Соединение..."</string>
<string name="vpn_connected">"Подключено, нажмите для проверки соединения"</string>
<string name="nat_connected">"Подключено"</string>
<string name="not_connected">"Не подключено"</string>
<!-- kcp -->
<string name="kcp_summary">"Включить Shadowsocks над KCPTUN"</string>
<string name="kcp_port">"KCP порт"</string>
<string name="kcp_port_summary">"%d (KCPTUN омер порта удалённого сервера)"</string>
<string name="kcpcli">"KCP параметры"</string>
<!-- acl -->
<string name="custom_rules">"Пользовательские правила"</string>
<string name="action_selection">"Выделение..."</string>
<string name="action_add_rule">"Добавить правило..."</string>
<string name="edit_rule">"Редактировать правило"</string>
<string name="route_entry_all">"Все"</string>
<string name="route_entry_bypass_lan">"Обход LAN"</string>
<string name="route_entry_bypass_chn">"Обходной Китай"</string>
<string name="route_entry_bypass_lan_chn">"Обход LAN и Китай"</string>
<string name="route_entry_bypass_lan">"Все, кроме LAN"</string>
<string name="route_entry_bypass_chn">"Все, кроме Китая"</string>
<string name="route_entry_bypass_lan_chn">"Все, кроме LAN и Китая"</string>
<string name="route_entry_chinalist">"Список Китай"</string>
<string name="proxied_apps_summary_v21">"Установить прокси для выбранных приложений"</string>
</resources>
<string name="acl_rule_templates_generic">"URL/Подсеть/Регулярное выражение (PCRE) имени хоста"</string>
<string name="acl_rule_templates_domain">"Доменное имя и все его поддомены"</string>
<!-- plugin -->
<string name="plugin">"Плагин"</string>
<string name="plugin_configure">"Настроить..."</string>
<string name="plugin_disabled">"Отключён"</string>
<string name="plugin_unknown">"Неизвестный плагин %s"</string>
<string name="plugin_untrusted">"Предупреждение: этот плагин получен из недоверенного источника."</string>
<string name="profile_plugin">"Плагин: %s"</string>
</resources>
+14 -20
View File
@@ -20,7 +20,6 @@
<string name="connection_test_error_status_code">"状态码无效(#%d)"</string>
<!-- proxy category -->
<string name="proxy_cat">"服务器设置"</string>
<string name="profile_name">"配置名称"</string>
<string name="proxy">"服务器"</string>
<string name="remote_port">"远程端口"</string>
@@ -29,11 +28,8 @@
<string name="enc_method">"加密方法"</string>
<!-- feature category -->
<string name="feature_cat">"功能设置"</string>
<string name="ipv6">"IPv6 路由"</string>
<string name="ipv6_summary">"向远程服务器转发 IPv6 流量"</string>
<string name="onetime_auth">"一次性认证"</string>
<string name="onetime_auth_summary">"启用一次性认证"</string>
<string name="route_list">"路由"</string>
<string name="route_entry_gfwlist">"仅代理中国大陆无法访问的地址"</string>
<string name="proxied_apps">"分应用代理"</string>
@@ -45,14 +41,13 @@
<string name="auto_connect_summary">"随系统启动后台服务"</string>
<string name="tcp_fastopen_summary">"切换需要 ROOT 权限"</string>
<string name="tcp_fastopen_summary_unsupported">"不支持的内核版本:%s &lt; 3.7.1"</string>
<string name="udp_dns">"UDP 转发"</string>
<string name="udp_dns_summary">"由远程服务器转发 UDP 协议的数据包"</string>
<string name="udp_dns">"DNS 转发"</string>
<string name="udp_dns_summary">"通过 UDP 将所有 DNS 查询全部转发至远程服务器"</string>
<!-- notification category -->
<string name="forward_success">"后台服务已开始运行。"</string>
<string name="invalid_server">"服务器名无效"</string>
<string name="service_failed">"无法连接远程服务器"</string>
<string name="method_unsafe">"警告:当前使用的加密算法不安全"</string>
<string name="nat_deprecated">"警告:在 Android 5.0 及更高版本中不建议使用 NAT 模式"</string>
<string name="nat_no_root">"NAT 模式需要 ROOT 权限"</string>
<string name="switch_to_vpn">"切换到 VPN 模式"</string>
@@ -63,14 +58,12 @@
<string name="profile_invalid_input">"未找到有效的配置文件。"</string>
<!-- alert category -->
<string name="yes">"是"</string>
<string name="no">"否"</string>
<string name="close">"关闭"</string>
<string name="profile_empty">"请选择配置文件"</string>
<string name="proxy_empty">"代理服务器地址及密码不能为空"</string>
<string name="connect">"连接"</string>
<string name="recovering">"重置中…"</string>
<string name="remove_profile">"删除此配置文件「%s」?"</string>
<string name="remove_profile">"删除此配置文件“%s”?"</string>
<!-- menu category -->
<string name="profiles">"配置文件"</string>
@@ -92,8 +85,6 @@
<!-- profile -->
<string name="profile_config">"配置文件设置"</string>
<string name="unsaved_changes_prompt">"保存修改吗?"</string>
<string name="apply">"应用"</string>
<string name="delete">"删除"</string>
<string name="delete_confirm_prompt">"您确定要删除此配置文件?"</string>
<string name="share_qr_nfc">"二维码 / NFC"</string>
@@ -101,6 +92,7 @@
<string name="add_profile_methods_scan_qr_code">"扫描二维码"</string>
<string name="add_profile_methods_manual_settings">"手动设置"</string>
<string name="add_profile_scanner_not_installed">"请安装任意兼容 ZXing 的二维码扫描应用。"</string>
<string name="add_profile_scanner_permission_required">扫描二维码需要相机权限。</string>
<plurals name="removed">
<item quantity="other">"已删除 %d 项"</item>
</plurals>
@@ -121,12 +113,6 @@
<string name="nat_connected">"已连接"</string>
<string name="not_connected">"未连接"</string>
<!-- kcp -->
<string name="kcp_summary">"启用 KCP 协议"</string>
<string name="kcp_port">"KCP 端口"</string>
<string name="kcp_port_summary">"%d(远端 KCPTUN 服务器的端口号)"</string>
<string name="kcpcli">"KCP 参数"</string>
<!-- acl -->
<string name="custom_rules">"自定义规则"</string>
<string name="action_selection">"选择…"</string>
@@ -138,6 +124,14 @@
<string name="route_entry_bypass_lan_chn">"绕过局域网及中国大陆地址"</string>
<string name="route_entry_chinalist">"仅代理中国大陆地址"</string>
<string name="proxied_apps_summary_v21">"为应用程序分别设置代理"</string>
<string name="acl_rule_templates_generic">"子网/域名 PCRE 正则表达式"</string>
<string name="acl_rule_templates_generic">"URL/子网/域名 PCRE 正则表达式"</string>
<string name="acl_rule_templates_domain">"域名及其子域名"</string>
</resources>
<!-- plugin -->
<string name="plugin">"插件"</string>
<string name="plugin_configure">"配置…"</string>
<string name="plugin_disabled">"禁用"</string>
<string name="plugin_unknown">"未知插件 %s"</string>
<string name="plugin_untrusted">"警告:该插件似乎并非来自已知的可信源。"</string>
<string name="profile_plugin">"插件:%s"</string>
</resources>
+14 -21
View File
@@ -2,8 +2,7 @@
<resources>
<string name="quick_toggle">"切換"</string>
<!-- global/misc category -->
<string name="misc_cat">"其他"</string>
<!-- misc -->
<string name="profile">"設定檔"</string>
<string name="profile_summary">"切換至其他設定檔或新增新設定檔"</string>
<string name="nat">"NAT 模式 (已過時)"</string>
@@ -19,7 +18,6 @@
<string name="connection_test_error_status_code">"錯誤碼: #%d"</string>
<!-- proxy category -->
<string name="proxy_cat">"伺服器設定"</string>
<string name="profile_name">"設定檔名稱"</string>
<string name="proxy">"伺服器"</string>
<string name="remote_port">"遠端連接埠"</string>
@@ -28,11 +26,8 @@
<string name="enc_method">"加密方法"</string>
<!-- feature category -->
<string name="feature_cat">"功能設定"</string>
<string name="ipv6">"IPv6 路由"</string>
<string name="ipv6_summary">"向遠端重新導向 IPv6 流量"</string>
<string name="onetime_auth">"單次驗證"</string>
<string name="onetime_auth_summary">"啟用單次驗證"</string>
<string name="route_list">"路由"</string>
<string name="route_entry_gfwlist">"GFW List"</string>
<string name="proxied_apps">"個別應用程式的 Proxy"</string>
@@ -44,14 +39,13 @@
<string name="auto_connect_summary">"在裝置啟動時啟用 Shadowsocks"</string>
<string name="tcp_fastopen_summary">"切換需要 ROOT 權限"</string>
<string name="tcp_fastopen_summary_unsupported">"不支援的核心版本:%s &lt; 3.7.1"</string>
<string name="udp_dns">"UDP 轉送"</string>
<string name="udp_dns_summary">"向遠端轉送 UDP 封包"</string>
<string name="udp_dns">"DNS 轉送"</string>
<string name="udp_dns_summary">"使用 UDP 向遠端轉送 DNS 封包"</string>
<!-- notification category -->
<string name="forward_success">"Shadowsocks 已啟動。"</string>
<string name="invalid_server">"伺服器名稱無效"</string>
<string name="service_failed">"連線至遠端伺服器失敗"</string>
<string name="method_unsafe">"警告:目前使用的加密方法不安全"</string>
<string name="nat_deprecated">"警告:自 Android 5.0 開始, NAT 模式已過時"</string>
<string name="nat_no_root">"NAT 模式需要 ROOT 權限"</string>
<string name="switch_to_vpn">"切換至 VPN 模式"</string>
@@ -62,8 +56,6 @@
<string name="profile_invalid_input">"未找到有效的設定檔資料。"</string>
<!-- alert category -->
<string name="yes">"是"</string>
<string name="no">"否"</string>
<string name="close">"關閉"</string>
<string name="profile_empty">"請選擇設定檔"</string>
<string name="proxy_empty">"Proxy 或密碼不可以空白"</string>
@@ -91,8 +83,6 @@
<!-- profile -->
<string name="profile_config">"設定檔設定"</string>
<string name="unsaved_changes_prompt">"要儲存變更嗎?"</string>
<string name="apply">"套用"</string>
<string name="delete">"刪除"</string>
<string name="delete_confirm_prompt">"您確定要移除這個設定檔嗎?"</string>
<string name="share_qr_nfc">"QR 碼 / NFC"</string>
@@ -100,6 +90,7 @@
<string name="add_profile_methods_scan_qr_code">"掃描 QR 碼"</string>
<string name="add_profile_methods_manual_settings">"手動設定"</string>
<string name="add_profile_scanner_not_installed">"請安裝任何相容 ZXing 的 QR 碼掃描應用程式。"</string>
<string name="add_profile_scanner_permission_required">掃描 QR 碼需要相機權限。</string>
<plurals name="removed">
<item quantity="other">"已移除 %d 項"</item>
</plurals>
@@ -120,12 +111,6 @@
<string name="nat_connected">"已連線"</string>
<string name="not_connected">"未連線"</string>
<!-- kcp -->
<string name="kcp_summary">"啟用 KCP 協定"</string>
<string name="kcp_port">"KCP 連接埠"</string>
<string name="kcp_port_summary">"%d(遠端 KCPTUN 伺服器的連接埠號碼)"</string>
<string name="kcpcli">"KCP 參數 (CLI)"</string>
<!-- acl -->
<string name="custom_rules">"自訂規則"</string>
<string name="action_selection">"選擇…"</string>
@@ -137,6 +122,14 @@
<string name="route_entry_bypass_lan_chn">"略過區域網路及中國大陸"</string>
<string name="route_entry_chinalist">"China List"</string>
<string name="proxied_apps_summary_v21">"為已選擇的應用程式設定 Proxy"</string>
<string name="acl_rule_templates_generic">"子網路/主機名稱 PCRE 模式"</string>
<string name="acl_rule_templates_generic">"URL/子網路/主機名稱 PCRE 模式"</string>
<string name="acl_rule_templates_domain">"網域及其所有子網域"</string>
</resources>
<!-- plugin -->
<string name="plugin">"插件"</string>
<string name="plugin_configure">"配置..."</string>
<string name="plugin_disabled">"未啟用"</string>
<string name="plugin_unknown">"未知插件 %s"</string>
<string name="plugin_untrusted">"警告:該插件似乎不是來自一個已知的可信任源。"</string>
<string name="profile_plugin">"插件: %s"</string>
</resources>
+2
View File
@@ -19,6 +19,7 @@
<item>AES-192-GCM</item>
<item>AES-256-GCM</item>
<item>CHACHA20-IETF-POLY1305</item>
<item>XCHACHA20-IETF-POLY1305</item>
</string-array>
<string-array name="enc_method_value">
@@ -40,6 +41,7 @@
<item>aes-192-gcm</item>
<item>aes-256-gcm</item>
<item>chacha20-ietf-poly1305</item>
<item>xchacha20-ietf-poly1305</item>
</string-array>
<string-array name="route_entry">
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#7488A1</color>
</resources>
+7 -5
View File
@@ -4,8 +4,7 @@
<string name="app_name">Shadowsocks</string>
<string name="quick_toggle">Toggle</string>
<!-- global/misc category -->
<string name="misc_cat">Misc</string>
<!-- misc -->
<string name="profile">Profile</string>
<string name="profile_summary">Switch to another profile or add new profiles</string>
<string name="nat">NAT mode (deprecated)</string>
@@ -53,10 +52,12 @@
<string name="auto_connect_summary">Enable Shadowsocks on startup</string>
<string name="tcp_fastopen_summary">Toggling requires ROOT permission</string>
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="udp_dns">UDP Forwarding</string>
<string name="udp_dns_summary">Forward UDP packets to remote</string>
<string name="udp_dns">DNS Forwarding</string>
<string name="udp_dns_summary">Forward all DNS requests to remote</string>
<!-- notification category -->
<string name="service_vpn">VPN Service</string>
<string name="service_nat">NAT Service</string>
<string name="forward_success">Shadowsocks started.</string>
<string name="invalid_server">Invalid server name</string>
<string name="service_failed">Failed to connect the remote server</string>
@@ -105,6 +106,7 @@
<string name="add_profile_methods_scan_qr_code">Scan QR code</string>
<string name="add_profile_methods_manual_settings">Manual Settings</string>
<string name="add_profile_scanner_not_installed">Please install any ZXing-compliant QR code scanning app.</string>
<string name="add_profile_scanner_permission_required">Camera permission is required for scanning QR code.</string>
<plurals name="removed">
<item quantity="one">Removed</item>
<item quantity="other">%d items removed</item>
@@ -130,7 +132,7 @@
<string name="custom_rules">Custom rules</string>
<string name="action_selection">Selection…</string>
<string name="action_add_rule">Add rule(s)…</string>
<string name="acl_rule_templates_generic">Subnet/Hostname PCRE pattern</string>
<string name="acl_rule_templates_generic">URL, Subnet or Hostname PCRE pattern</string>
<string name="acl_rule_templates_domain">Domain name and all its subdomain names</string>
<string name="edit_rule">Edit rule</string>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
<entry>
<key>proxy_url</key>
<value>https://www.socks123.pw/get.php</value>
</entry>
</defaultsMap>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false"/>
</network-security-config>
@@ -34,7 +34,7 @@ class AboutFragment extends ToolbarFragment {
override def onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState)
toolbar.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, BuildConfig.VERSION_NAME))
val web = view.findViewById(R.id.web_view).asInstanceOf[WebView]
val web = view.findViewById[WebView](R.id.web_view)
web.loadUrl("file:///android_asset/pages/about.html")
web.setWebViewClient(new WebViewClient() {
override def shouldOverrideUrlLoading(view: WebView, url: String): Boolean = {
@@ -70,8 +70,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
import AppManager._
private final class AppViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private val icon = itemView.findViewById(R.id.itemicon).asInstanceOf[ImageView]
private val check = itemView.findViewById(R.id.itemcheck).asInstanceOf[Switch]
private val icon = itemView.findViewById[ImageView](R.id.itemicon)
private val check = itemView.findViewById[Switch](R.id.itemcheck)
private var item: ProxiedApp = _
itemView.setOnClickListener(this)
@@ -92,7 +92,10 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
proxiedApps.add(item.packageName)
check.setChecked(true)
}
if (!appsLoading.get) app.editor.putString(Key.individual, proxiedApps.mkString("\n")).apply()
if (!appsLoading.get) {
app.dataStore.individual = proxiedApps.mkString("\n")
app.dataStore.dirty = true
}
}
}
@@ -118,7 +121,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
private val appsLoading = new AtomicBoolean
private var handler: Handler = _
private def initProxiedApps(str: String = app.settings.getString(Key.individual, null)) =
private def initProxiedApps(str: String = app.dataStore.individual) =
proxiedApps = str.split('\n').to[mutable.HashSet]
override def onDestroy() {
@@ -136,7 +139,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
case R.id.action_apply_all =>
app.profileManager.getAllProfiles match {
case Some(profiles) =>
val proxiedAppString = app.settings.getString(Key.individual, null)
val proxiedAppString = app.dataStore.individual
profiles.foreach(p => {
p.individual = proxiedAppString
app.profileManager.updateProfile(p)
@@ -146,10 +149,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
}
return true
case R.id.action_export =>
val bypass = app.settings.getBoolean(Key.bypass, false)
val proxiedAppString = app.settings.getString(Key.individual, null)
val clip = ClipData.newPlainText(Key.individual, bypass + "\n" + proxiedAppString)
clipboard.setPrimaryClip(clip)
clipboard.setPrimaryClip(ClipData.newPlainText(Key.individual,
app.dataStore.bypass + "\n" + app.dataStore.individual))
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
return true
case R.id.action_import =>
@@ -163,7 +164,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
val (enabled, apps) = if (i < 0) (proxiedAppString, "")
else (proxiedAppString.substring(0, i), proxiedAppString.substring(i + 1))
bypassSwitch.setChecked(enabled.toBoolean)
app.editor.putString(Key.individual, apps).putBoolean(Key.dirty, true).apply()
app.dataStore.individual = apps
app.dataStore.dirty = true
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
appListView.setVisibility(View.GONE)
fastScroller.setVisibility(View.GONE)
@@ -202,18 +204,23 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
toolbar.inflateMenu(R.menu.app_manager_menu)
toolbar.setOnMenuItemClickListener(this)
if (!app.settings.getBoolean(Key.proxyApps, false))
app.editor.putBoolean(Key.proxyApps, true).putBoolean(Key.dirty, true).apply()
if (!app.dataStore.proxyApps) {
app.dataStore.proxyApps = true
app.dataStore.dirty = true
}
findViewById(R.id.onSwitch).asInstanceOf[Switch]
.setOnCheckedChangeListener((_, checked) => {
app.editor.putBoolean(Key.proxyApps, checked).putBoolean(Key.dirty, true).apply()
app.dataStore.proxyApps = checked
app.dataStore.dirty = true
finish()
})
bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
bypassSwitch.setChecked(app.settings.getBoolean(Key.bypass, false))
bypassSwitch.setOnCheckedChangeListener((_, checked) =>
app.editor.putBoolean(Key.bypass, checked).putBoolean(Key.dirty, true).apply())
bypassSwitch.setChecked(app.dataStore.bypass)
bypassSwitch.setOnCheckedChangeListener((_, checked) => {
app.dataStore.bypass = checked
app.dataStore.dirty = true
})
initProxiedApps()
loadingView = findViewById(R.id.loading)
@@ -21,14 +21,14 @@
package com.github.shadowsocks
import java.io.{File, IOException}
import java.net.InetAddress
import java.net.{Inet6Address, InetAddress}
import java.util
import java.util.concurrent.TimeUnit
import java.util.{Timer, TimerTask}
import android.app.Service
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
import android.os.{Handler, IBinder, RemoteCallbackList}
import android.content._
import android.os.{Build, Handler, IBinder, RemoteCallbackList}
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
@@ -64,9 +64,12 @@ trait BaseService extends Service {
lazy val handler = new Handler(getMainLooper)
lazy val restartHanlder = new Handler(getMainLooper)
private val closeReceiver: BroadcastReceiver = (context: Context, _: Intent) => {
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
stopRunner(stopService = true)
private var notification: ShadowsocksNotification = _
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => intent.getAction match {
case Action.RELOAD => forceLoad()
case _ =>
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
stopRunner(stopService = true)
}
var closeReceiverRegistered: Boolean = _
@@ -102,20 +105,6 @@ trait BaseService extends Service {
stopListeningForBandwidth(cb) // saves an RPC, and safer
callbacks.unregister(cb)
}
override def use(profileId: Int): Unit = synchronized(if (profileId < 0) stopRunner(stopService = true) else {
val profile = app.profileManager.getProfile(profileId).orNull
if (profile == null) stopRunner(stopService = true, getString(R.string.profile_empty)) else state match {
case State.STOPPED => if (checkProfile(profile)) startRunner(profile)
case State.CONNECTED => if (profileId != BaseService.this.profile.id && checkProfile(profile)) {
stopRunner(stopService = false)
startRunner(profile)
}
case _ => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + state)
}
})
override def useSync(profileId: Int): Unit = use(profileId)
}
def checkProfile(profile: Profile): Boolean = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
@@ -123,27 +112,33 @@ trait BaseService extends Service {
false
} else true
def forceLoad(): Unit = app.currentProfile.orNull match {
case null => stopRunner(stopService = true, getString(R.string.profile_empty))
case p => if (checkProfile(p)) state match {
case State.STOPPED => startRunner()
case State.CONNECTED =>
stopRunner(stopService = false)
startRunner()
case s => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + s)
}
}
def connect() {
if (profile.host == "198.199.101.152") {
val holder = app.containerHolder
val container = holder.getContainer
val url = container.getString("proxy_url")
val sig = Utils.getSignature(this)
val client = new OkHttpClient.Builder()
.dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match {
case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip))
case _ => Dns.SYSTEM.lookup(hostname)
})
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val requestBody = new FormBody.Builder()
.add("sig", sig)
.add("sig", Utils.getSignature(this))
.build()
val request = new Request.Builder()
.url(url)
.url(app.remoteConfig.getString("proxy_url"))
.post(requestBody)
.build()
@@ -156,42 +151,15 @@ trait BaseService extends Service {
}
if (profile.route == Acl.CUSTOM_RULES) // rationalize custom rules
Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES))
Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES), true)
plugin = new PluginConfiguration(profile.plugin).selectedOptions
pluginPath = PluginManager.init(plugin)
}
def startRunner(profile: Profile) {
this.profile = profile
startService(new Intent(this, getClass))
TrafficMonitor.reset()
trafficMonitorThread = new TrafficMonitorThread(getApplicationContext)
trafficMonitorThread.start()
if (!closeReceiverRegistered) {
// register close receiver
val filter = new IntentFilter()
filter.addAction(Intent.ACTION_SHUTDOWN)
filter.addAction(Action.CLOSE)
registerReceiver(closeReceiver, filter)
closeReceiverRegistered = true
}
app.track(getClass.getSimpleName, "start")
changeState(State.CONNECTING)
Utils.ThrowableFuture(try connect() catch {
case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
case exc: Throwable =>
stopRunner(stopService = true, getString(R.string.service_failed) + ": " + exc.getMessage)
exc.printStackTrace()
app.track(exc)
})
}
def createNotification(): ShadowsocksNotification
def startRunner(): Unit = if (Build.VERSION.SDK_INT >= 26) startForegroundService(new Intent(this, getClass))
else startService(new Intent(this, getClass))
def stopRunner(stopService: Boolean, msg: String = null) {
// clean up recevier
@@ -200,6 +168,8 @@ trait BaseService extends Service {
closeReceiverRegistered = false
}
if (notification != null) notification.destroy()
// Make sure update total traffic when stopping the runner
updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
@@ -274,12 +244,53 @@ trait BaseService extends Service {
override def onCreate() {
super.onCreate()
app.refreshContainerHolder()
app.updateAssets()
}
// Service of shadowsocks should always be started explicitly
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_NOT_STICKY
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
state match {
case State.STOPPED | State.IDLE =>
case _ => return Service.START_NOT_STICKY // ignore request
}
profile = app.currentProfile.orNull
if (profile == null) {
stopRunner(stopService = true)
return Service.START_NOT_STICKY
}
profile.name = profile.getName // save original name before it's (possibly) overwritten by IP addresses
TrafficMonitor.reset()
trafficMonitorThread = new TrafficMonitorThread(getApplicationContext)
trafficMonitorThread.start()
if (!closeReceiverRegistered) {
// register close receiver
val filter = new IntentFilter()
filter.addAction(Action.RELOAD)
filter.addAction(Intent.ACTION_SHUTDOWN)
filter.addAction(Action.CLOSE)
registerReceiver(closeReceiver, filter)
closeReceiverRegistered = true
}
notification = createNotification()
app.track(getClass.getSimpleName, "start")
changeState(State.CONNECTING)
Utils.ThrowableFuture(try connect() catch {
case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
case exc: Throwable =>
stopRunner(stopService = true, getString(R.string.service_failed) + ": " + exc.getMessage)
exc.printStackTrace()
app.track(exc)
})
Service.START_NOT_STICKY
}
protected def changeState(s: Int, msg: String = null) {
val handler = new Handler(getMainLooper)
@@ -299,23 +310,12 @@ trait BaseService extends Service {
}
}
def getBlackList: String = {
val default = getString(R.string.black_list)
try {
val container = app.containerHolder.getContainer
val update = container.getString("black_list_lite")
val list = if (update == null || update.isEmpty) default else update
"exclude = " + list + ";"
} catch {
case _: Exception => "exclude = " + default + ";"
}
}
protected def buildPluginCommandLine(): ArrayBuffer[String] = {
val result = ArrayBuffer(pluginPath)
if (TcpFastOpen.sendEnabled) result += "--fast-open"
result
}
protected final def buildShadowsocksConfig(file: String): String = {
val config = new JSONObject()
.put("server", profile.host)
@@ -330,20 +330,8 @@ trait BaseService extends Service {
}
protected final def buildOvertureConfig(file: String): String = {
val url = "http://icanhazip.com"
val client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val request = new Request.Builder()
.url(url)
.build()
val response = client.newCall(request).execute()
val externalIp = if (response.isSuccessful) response.body.string.trim else ""
val config = new JSONObject()
.put("BindAddress", ":" + (profile.localPort + 53))
.put("BindAddress", "127.0.0.1:" + (profile.localPort + 53))
.put("RedirectIPv6Record", true)
.put("DomainBase64Decode", true)
.put("HostsFile", "hosts")
@@ -352,34 +340,46 @@ trait BaseService extends Service {
def makeDns(name: String, address: String, edns: Boolean = true) = {
val dns = new JSONObject()
.put("Name", name)
.put("Address", address + ":53")
.put("Address", (Utils.parseNumericAddress(address) match {
case _: Inet6Address => '[' + address + ']'
case _ => address
}) + ":53")
.put("Timeout", 6)
.put("EDNSClientSubnet", new JSONObject().put("Policy", "disable"))
if (edns) dns
.put("Protocol", "tcp")
.put("Socks5Address", "127.0.0.1:" + profile.localPort)
.put("EDNSClientSubnet", new JSONObject()
.put("Policy", "auto")
.put("ExternalIP", externalIp))
else dns
.put("Protocol", "udp")
.put("EDNSClientSubnet", new JSONObject().put("Policy", "disable"))
else dns.put("Protocol", "udp")
dns
}
val remoteDns = new JSONArray(profile.remoteDns.split(",").zipWithIndex.map {
case (dns, i) => makeDns("UserDef-" + i, dns.trim)
})
val localDns = new JSONArray(Array(
makeDns("Primary-1", "119.29.29.29", edns = false),
makeDns("Primary-2", "114.114.114.114", edns = false)
))
try {
val localLinkDns = com.github.shadowsocks.utils.Dns.getDnsResolver(this)
localDns.put(makeDns("Primary-3", localLinkDns, edns = false))
} catch {
case _: Exception => // Ignore
}
profile.route match {
case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => config
.put("PrimaryDNS", new JSONArray(Array(
makeDns("Primary-1", "119.29.29.29", edns = false),
makeDns("Primary-2", "114.114.114.114", edns = false)
)))
.put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", profile.remoteDns)))
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", "gfwlist.txt")
case Acl.CHINALIST => config
.put("PrimaryDNS", new JSONArray().put(makeDns("Primary", "119.29.29.29")))
.put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", profile.remoteDns)))
.put("AlternativeDNS", remoteDns)
case _ => config
.put("PrimaryDNS", new JSONArray().put(makeDns("Primary", profile.remoteDns)))
.put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", "208.67.222.222")))
.put("PrimaryDNS", remoteDns)
// no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode
.put("OnlyPrimaryDNS", true)
}
IOUtils.writeString(new File(getFilesDir, file), config.toString)
file
@@ -20,27 +20,24 @@
package com.github.shadowsocks
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.PreferenceDataStore
import be.mygod.preference.PreferenceFragment
import com.github.shadowsocks.utils.{Key, TcpFastOpen}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.{Key, TcpFastOpen}
class GlobalConfigFragment extends PreferenceFragment with OnSharedPreferenceChangeListener {
class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStoreChangeListener {
override def onCreatePreferences(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
addPreferencesFromResource(R.xml.pref_global)
val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference]
switch.setOnPreferenceChangeListener((_, value) => {
BootReceiver.setEnabled(getActivity, value.asInstanceOf[Boolean])
true
})
if (getPreferenceManager.getSharedPreferences.getBoolean(Key.isAutoConnect, false)) {
BootReceiver.setEnabled(getActivity, true)
getPreferenceManager.getSharedPreferences.edit.remove(Key.isAutoConnect).apply()
}
switch.setChecked(BootReceiver.getEnabled(getActivity))
val tfo = findPreference(Key.tfo).asInstanceOf[SwitchPreference]
@@ -56,16 +53,16 @@ class GlobalConfigFragment extends PreferenceFragment with OnSharedPreferenceCha
tfo.setEnabled(false)
tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version")))
}
app.settings.registerOnSharedPreferenceChangeListener(this)
app.dataStore.registerChangeListener(this)
}
override def onDestroy() {
app.settings.unregisterOnSharedPreferenceChangeListener(this)
app.dataStore.unregisterChangeListener(this)
super.onDestroy()
}
def onSharedPreferenceChanged(pref: SharedPreferences, key: String): Unit = key match {
case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(pref.getBoolean(key, false))
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(store.getBoolean(key, false))
case _ =>
}
}
@@ -33,9 +33,9 @@ import com.github.shadowsocks.ShadowsocksApplication.app
import scala.collection.JavaConversions._
import scala.collection.immutable.Stream
class StreamLogger(is: InputStream, tag: String) extends Thread {
class StreamLogger(is: InputStream, tag: String, logger: (String, String) => Int) extends Thread {
override def run(): Unit = autoClose(new BufferedReader(new InputStreamReader(is)))(br =>
try Stream.continually(br.readLine()).takeWhile(_ != null).foreach(Log.i(tag, _)) catch {
try Stream.continually(br.readLine()).takeWhile(_ != null).foreach(logger(tag, _)) catch {
case _: IOException =>
})
}
@@ -68,8 +68,8 @@ class GuardedProcess(cmd: String*) {
.directory(app.getFilesDir)
.start()
val is = process.getInputStream
new StreamLogger(is, TAG).start()
new StreamLogger(process.getInputStream(), TAG, Log.i).start()
new StreamLogger(process.getErrorStream(), TAG, Log.e).start()
if (callback == null) callback = onRestartCallback else callback()
@@ -26,7 +26,6 @@ import java.util.Locale
import android.app.backup.BackupManager
import android.app.{Activity, ProgressDialog}
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content._
import android.net.{Uri, VpnService}
import android.nfc.{NdefMessage, NfcAdapter}
@@ -36,6 +35,7 @@ import android.support.design.widget.{FloatingActionButton, Snackbar}
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.RecyclerView.ViewHolder
import android.text.TextUtils
import android.util.Log
@@ -45,6 +45,7 @@ import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, CustomRulesFragment}
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.CloseUtils.autoDisconnect
import com.github.shadowsocks.utils._
import com.mikepenz.crossfader.Crossfader
@@ -67,7 +68,7 @@ object MainActivity {
}
class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
with OnSharedPreferenceChangeListener {
with OnPreferenceDataStoreChangeListener {
import MainActivity._
// UI
@@ -165,7 +166,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
override def onServiceDisconnected(): Unit = changeState(State.IDLE)
private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ =>
if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener)
if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener)
override def binderDied(): Unit = handler.post(() => {
detachService()
@@ -174,7 +175,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
})
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match {
case Activity.RESULT_OK => bgService.use(app.profileId)
case Activity.RESULT_OK => Utils.startSsService(this)
case _ => Log.e(TAG, "Failed to start VpnService")
}
@@ -246,7 +247,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
} else drawer = drawerBuilder.build()
val header = drawer.getHeader
val title = header.findViewById(R.id.drawer_title).asInstanceOf[TextView]
val title = header.findViewById[TextView](R.id.drawer_title)
val tf = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf)
@@ -256,7 +257,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
txRateText = findViewById(R.id.txRate).asInstanceOf[TextView]
rxText = findViewById(R.id.rx).asInstanceOf[TextView]
rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView]
findViewById(R.id.stat).setOnClickListener(_ => if (state == State.CONNECTED && app.isVpnEnabled) {
findViewById[View](R.id.stat).setOnClickListener(_ => if (state == State.CONNECTED && app.isVpnEnabled) {
testCount += 1
statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code
@@ -298,8 +299,8 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
fab.setOnClickListener(_ => if (state == State.CONNECTED) bgService.use(-1) else Utils.ThrowableFuture {
if (app.isNatEnabled) bgService.use(app.profileId) else {
fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture {
if (app.isNatEnabled) Utils.startSsService(this) else {
val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null))
@@ -313,7 +314,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
changeState(State.IDLE) // reset everything to init state
handler.post(() => attachService(callback))
app.settings.registerOnSharedPreferenceChangeListener(this)
app.dataStore.registerChangeListener(this)
val intent = getIntent
if (intent != null) handleShareIntent(intent)
@@ -350,7 +351,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
.show()
}
def onSharedPreferenceChanged(pref: SharedPreferences, key: String): Unit = key match {
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => handler.post(() => {
detachService()
attachService(callback)
@@ -368,7 +369,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
case DRAWER_PROFILES => displayFragment(new ProfilesFragment)
case DRAWER_RECOVERY =>
app.track("GlobalConfigFragment", "reset")
if (bgService != null) bgService.use(-1)
Utils.stopSsService(this)
val dialog = ProgressDialog.show(this, "", getString(R.string.recovering), true, false)
val handler = new Handler {
override def handleMessage(msg: Message): Unit = if (dialog.isShowing && !isDestroyed) dialog.dismiss()
@@ -390,7 +391,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
protected override def onResume() {
super.onResume()
app.refreshContainerHolder()
app.remoteConfig.fetch()
state match {
case State.STOPPING | State.CONNECTING =>
case _ => hideCircle()
@@ -424,7 +425,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
override def onDestroy() {
super.onDestroy()
app.settings.unregisterOnSharedPreferenceChangeListener(this)
app.dataStore.unregisterChangeListener(this)
detachService()
new BackupManager(this).dataChanged()
handler.removeCallbacksAndMessages(null)
@@ -49,7 +49,7 @@ class ProfileConfigActivity extends Activity {
toolbar.setOnMenuItemClickListener(child)
}
override def onBackPressed(): Unit = if (app.settings.getBoolean(Key.dirty, false)) new AlertDialog.Builder(this)
override def onBackPressed(): Unit = if (app.dataStore.dirty) new AlertDialog.Builder(this)
.setTitle(R.string.unsaved_changes_prompt)
.setPositiveButton(R.string.yes, ((_, _) => child.saveAndExit()): DialogInterface.OnClickListener)
.setNegativeButton(R.string.no, ((_, _) => finish()): DialogInterface.OnClickListener)
@@ -21,13 +21,12 @@
package com.github.shadowsocks
import android.app.Activity
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content._
import android.os.{Build, Bundle, UserManager}
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.app.AlertDialog
import android.support.v7.preference.Preference
import android.support.v7.preference.{Preference, PreferenceDataStore}
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.text.TextUtils
import android.view.MenuItem
@@ -35,7 +34,7 @@ import be.mygod.preference.{EditTextPreference, PreferenceFragment}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin._
import com.github.shadowsocks.preference.{IconListPreference, PluginConfigurationDialogFragment}
import com.github.shadowsocks.preference.{IconListPreference, OnPreferenceDataStoreChangeListener, PluginConfigurationDialogFragment}
import com.github.shadowsocks.utils.{Action, Key, Utils}
object ProfileConfigFragment {
@@ -43,7 +42,7 @@ object ProfileConfigFragment {
}
class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener
with OnSharedPreferenceChangeListener {
with OnPreferenceDataStoreChangeListener {
import ProfileConfigFragment._
private var profile: Profile = _
@@ -53,10 +52,11 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
private var pluginConfiguration: PluginConfiguration = _
override def onCreatePreferences(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match {
case Some(p) =>
profile = p
profile.serialize(app.editor).apply()
profile.serialize(app.dataStore)
case None => getActivity.finish()
}
addPreferencesFromResource(R.xml.pref_profile)
@@ -78,7 +78,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
plugin.setOnPreferenceChangeListener((_, value) => {
val selected = value.asInstanceOf[String]
pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions, selected)
app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply()
app.dataStore.plugin = pluginConfiguration.toString
app.dataStore.dirty = true
pluginConfigure.setEnabled(!TextUtils.isEmpty(selected))
pluginConfigure.setText(pluginConfiguration.selectedOptions.toString)
if (!PluginManager.fetchPlugins()(selected).trusted)
@@ -88,7 +89,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
pluginConfigure.setOnPreferenceChangeListener(onPluginConfigureChanged)
initPlugins()
app.listenForPackageChanges(initPlugins())
app.settings.registerOnSharedPreferenceChangeListener(this)
app.dataStore.registerChangeListener(this)
}
def initPlugins() {
@@ -97,7 +98,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
plugin.setEntryValues(plugins.map(_._2.id.asInstanceOf[CharSequence]).toArray)
plugin.setEntryIcons(plugins.map(_._2.icon).toArray)
plugin.entryPackageNames = plugins.map(_._2.packageName).toArray
pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null))
pluginConfiguration = new PluginConfiguration(app.dataStore.plugin)
plugin.setValue(pluginConfiguration.selected)
plugin.init()
plugin.checkSummary()
@@ -109,7 +110,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
val selected = pluginConfiguration.selected
pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions +
(pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected)
app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply()
app.dataStore.plugin = pluginConfiguration.toString
app.dataStore.dirty = true
true
} catch {
case exc: IllegalArgumentException =>
@@ -117,17 +119,17 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
false
}
override def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String): Unit =
if (key != Key.proxyApps && findPreference(key) != null) app.editor.putBoolean(Key.dirty, true).apply()
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit =
if (key != Key.proxyApps && findPreference(key) != null) app.dataStore.dirty = true
override def onDestroy() {
app.settings.unregisterOnSharedPreferenceChangeListener(this)
app.dataStore.unregisterChangeListener(this)
super.onDestroy()
}
override def onResume() {
super.onResume()
isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager
isProxyApps.setChecked(app.dataStore.proxyApps) // fetch proxyApps updated by AppManager
}
private def showPluginEditor() {
@@ -176,7 +178,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
}
def saveAndExit() {
profile.deserialize(app.settings)
profile.deserialize(app.dataStore)
app.profileManager.updateProfile(profile)
if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.deepRefreshId(profile.id)
getActivity.finish()
@@ -68,7 +68,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
case _ => false
}
private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED => id != app.profileId
case State.CONNECTED => id != app.dataStore.profileId
case State.STOPPED => true
case _ => false
}
@@ -78,20 +78,20 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
var item: Profile = _
private val text1 = itemView.findViewById(android.R.id.text1).asInstanceOf[TextView]
private val text2 = itemView.findViewById(android.R.id.text2).asInstanceOf[TextView]
private val traffic = itemView.findViewById(R.id.traffic).asInstanceOf[TextView]
private val edit = itemView.findViewById(R.id.edit)
private val text1 = itemView.findViewById[TextView](android.R.id.text1)
private val text2 = itemView.findViewById[TextView](android.R.id.text2)
private val traffic = itemView.findViewById[TextView](R.id.traffic)
private val edit = itemView.findViewById[View](R.id.edit)
edit.setOnClickListener(_ => startConfig(item.id))
edit.setOnLongClickListener(cardButtonLongClickListener)
itemView.setOnClickListener(this)
// it will not take effect unless set in code
itemView.findViewById(R.id.indicator).setBackgroundResource(R.drawable.background_profile)
itemView.findViewById[View](R.id.indicator).setBackgroundResource(R.drawable.background_profile)
private var adView: NativeExpressAdView = _
{
val share = itemView.findViewById(R.id.share)
val share = itemView.findViewById[View](R.id.share)
share.setOnClickListener(_ => {
val popup = new PopupMenu(getActivity, share)
popup.getMenuInflater.inflate(R.menu.profile_share_popup, popup.getMenu)
@@ -129,7 +129,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
TrafficMonitor.formatTraffic(tx), TrafficMonitor.formatTraffic(rx)))
}
if (item.id == app.profileId) {
if (item.id == app.dataStore.profileId) {
itemView.setSelected(true)
selectedItem = this
} else {
@@ -147,7 +147,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
adView.setLayoutParams(params)
adView.setAdUnitId("ca-app-pub-9097031975646651/5224027521")
adView.setAdSize(new AdSize(328, 132))
itemView.findViewById(R.id.content).asInstanceOf[LinearLayout].addView(adView)
itemView.findViewById[LinearLayout](R.id.content).addView(adView)
// Demographics
val random = new Random()
@@ -166,11 +166,11 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
def onClick(v: View): Unit = if (isEnabled) {
val activity = getActivity.asInstanceOf[MainActivity]
val old = app.profileId
val old = app.dataStore.profileId
app.switchProfile(item.id)
profilesAdapter.refreshId(old)
itemView.setSelected(true)
if (activity.state == State.CONNECTED) activity.bgService.use(item.id) // reconnect to new profile
if (activity.state == State.CONNECTED) Utils.reloadSsService(activity)
}
override def onMenuItemClick(menu: MenuItem): Boolean = menu.getItemId match {
@@ -251,7 +251,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
if (index >= 0) {
profiles.remove(index)
notifyItemRemoved(index)
if (id == app.profileId) app.profileId(0) // switch to null profile
if (id == app.dataStore.profileId) app.dataStore.profileId = 0 // switch to null profile
}
}
}
@@ -278,12 +278,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
toolbar.inflateMenu(R.menu.profile_manager_menu)
toolbar.setOnMenuItemClickListener(this)
if (app.profileManager.getFirstProfile.isEmpty) app.profileId(app.profileManager.createProfile().id)
val profilesList = view.findViewById(R.id.list).asInstanceOf[RecyclerView]
if (app.profileManager.getFirstProfile.isEmpty) app.dataStore.profileId = app.profileManager.createProfile().id
val profilesList = view.findViewById[RecyclerView](R.id.list)
val layoutManager = new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false)
profilesList.setLayoutManager(layoutManager)
layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == app.profileId => i
case (profile, i) if profile.id == app.dataStore.profileId => i
}.getOrElse(-1))
val animator = new DefaultItemAnimator()
animator.setSupportsChangeAnimations(false) // prevent fading-in/out when rebinding
@@ -357,10 +357,11 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_DOCUMENT),
REQUEST_SCAN_QR_CODE) catch {
case _: ActivityNotFoundException =>
Toast.makeText(getActivity, R.string.add_profile_scanner_not_installed, Toast.LENGTH_LONG).show()
try startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark"))) catch {
case exc: ActivityNotFoundException => exc.printStackTrace()
}
startActivity(new Intent(getActivity, classOf[ScannerActivity]))
case e: SecurityException =>
e.printStackTrace()
app.track(e)
startActivity(new Intent(getActivity, classOf[ScannerActivity]))
}
true
case R.id.action_import =>
@@ -24,6 +24,8 @@ import android.app.Activity
import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.{Build, Bundle}
import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat}
import android.support.v4.graphics.drawable.IconCompat
import com.github.shadowsocks.utils.{State, Utils}
/**
@@ -34,11 +36,12 @@ class QuickToggleShortcut extends Activity with ServiceBoundContext {
super.onCreate(savedInstanceState)
getIntent.getAction match {
case Intent.ACTION_CREATE_SHORTCUT =>
setResult(Activity.RESULT_OK, new Intent()
.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(this, classOf[QuickToggleShortcut]))
.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.quick_toggle))
.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher)))
setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this,
new ShortcutInfoCompat.Builder(this, "toggle")
.setIntent(new Intent(this, classOf[QuickToggleShortcut]).setAction(Intent.ACTION_MAIN))
.setIcon(IconCompat.createWithResource(this, R.drawable.ic_qu_shadowsocks_launcher))
.setShortLabel(getString(R.string.quick_toggle))
.build()))
finish()
case _ =>
attachService()
@@ -0,0 +1,105 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2016 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2016 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* This program is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*******************************************************************************/
package com.github.shadowsocks
import android.app.{Activity, TaskStackBuilder}
import android.content.Intent
import android.content.pm.{PackageManager, ShortcutManager}
import android.os.{Build, Bundle}
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.widget.Toast
import com.google.zxing.Result
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Parser
import me.dm7.barcodescanner.zxing.ZXingScannerView
object ScannerActivity {
private final val MY_PERMISSIONS_REQUEST_CAMERA = 1
}
class ScannerActivity extends AppCompatActivity with ZXingScannerView.ResultHandler {
import ScannerActivity._
private var scannerView: ZXingScannerView = _
override def onRequestPermissionsResult(requestCode: Int, permissions: Array[String],
grantResults: Array[Int]) {
if (requestCode == MY_PERMISSIONS_REQUEST_CAMERA) {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults(0) == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this)
scannerView.startCamera()
} else {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
finish()
}
}
}
def navigateUp() {
val intent = getParentActivityIntent
if (shouldUpRecreateTask(intent) || isTaskRoot)
TaskStackBuilder.create(this).addNextIntentWithParentStack(intent).startActivities()
else finish()
}
override def onCreate(state: Bundle) {
super.onCreate(state)
setContentView(R.layout.layout_scanner)
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(getTitle)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
toolbar.setNavigationOnClickListener(_ => navigateUp())
scannerView = findViewById(R.id.scanner).asInstanceOf[ZXingScannerView]
if (Build.VERSION.SDK_INT >= 25) getSystemService(classOf[ShortcutManager]).reportShortcutUsed("scan")
}
override def onResume() {
super.onResume()
val permissionCheck = ContextCompat.checkSelfPermission(this,
android.Manifest.permission.CAMERA)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
scannerView.startCamera() // Start camera on resume
} else {
ActivityCompat.requestPermissions(this,
Array(android.Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA)
}
}
override def onPause() {
super.onPause()
scannerView.stopCamera() // Stop camera on pause
}
override def handleResult(rawResult: Result) = {
val uri = rawResult.getText
if (!TextUtils.isEmpty(uri))
Parser.findAll(uri).foreach(app.profileManager.createProfile)
navigateUp()
}
}
@@ -21,29 +21,28 @@
package com.github.shadowsocks
import java.io.{File, FileOutputStream, IOException}
import java.util
import java.util.Locale
import java.util.concurrent.TimeUnit
import android.annotation.SuppressLint
import android.app.Application
import android.app.{Application, NotificationChannel, NotificationManager}
import android.content._
import android.content.res.Configuration
import android.os.{Build, LocaleList}
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.DonaldTrump
import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager}
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker}
import com.google.android.gms.common.api.ResultCallback
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.j256.ormlite.logger.LocalLog
import eu.chainfire.libsuperuser.Shell
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
object ShadowsocksApplication {
@@ -61,14 +60,13 @@ object ShadowsocksApplication {
class ShadowsocksApplication extends Application {
import ShadowsocksApplication._
final val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = _
lazy val remoteConfig: FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
lazy val tracker: Tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
lazy val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
lazy val editor: SharedPreferences.Editor = settings.edit
lazy val profileManager = new ProfileManager(new DBHelper(this))
private lazy val dbHelper = new DBHelper(this)
lazy val profileManager = new ProfileManager(dbHelper)
lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper)
def isNatEnabled: Boolean = settings.getBoolean(Key.isNAT, false)
def isNatEnabled: Boolean = dataStore.isNAT
def isVpnEnabled: Boolean = !isNatEnabled
// send event
@@ -81,13 +79,11 @@ class ShadowsocksApplication extends Application {
.setFatal(false)
.build())
def profileId: Int = settings.getInt(Key.id, 0)
def profileId(i: Int): Unit = editor.putInt(Key.id, i).apply()
def currentProfile: Option[Profile] = profileManager.getProfile(profileId)
def currentProfile: Option[Profile] = profileManager.getProfile(dataStore.profileId)
def switchProfile(id: Int): Profile = {
val result = profileManager.getProfile(id) getOrElse profileManager.createProfile()
profileId(result.id)
dataStore.profileId = result.id
result
}
@@ -149,33 +145,19 @@ class ShadowsocksApplication extends Application {
if (!BuildConfig.DEBUG) java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
checkChineseLocale(getResources.getConfiguration)
val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] {
override def onResult(holder: ContainerHolder) {
if (!holder.getStatus.isSuccess) {
return
}
containerHolder = holder
val container = holder.getContainer
container.registerFunctionCallMacroCallback(SIG_FUNC,
(functionName: String, parameters: util.Map[String, AnyRef]) => {
if (functionName == SIG_FUNC) {
Utils.getSignature(getApplicationContext)
}
null
})
}
}
pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
FirebaseApp.initializeApp(this)
remoteConfig.setDefaults(R.xml.default_configs)
remoteConfig.fetch().addOnCompleteListener(task => if (task.isSuccessful) remoteConfig.activateFetched())
JobManager.create(this).addJobCreator(DonaldTrump)
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
}
TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
def refreshContainerHolder() {
val holder = app.containerHolder
if (holder != null) holder.refresh()
if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List(
new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN),
new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW)
))
}
def crashRecovery() {
@@ -208,10 +190,10 @@ class ShadowsocksApplication extends Application {
autoClose(new FileOutputStream(new File(getFilesDir, file)))(out =>
IOUtils.copy(in, out)))
}
editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply()
dataStore.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE)
}
def updateAssets(): Unit = if (settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets()
def updateAssets(): Unit = if (dataStore.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets()
def listenForPackageChanges(callback: => Unit): BroadcastReceiver = {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
@@ -20,26 +20,11 @@
package com.github.shadowsocks
import android.app.backup.{BackupAgentHelper, FileBackupHelper, SharedPreferencesBackupHelper}
import android.app.backup.{BackupAgentHelper, FileBackupHelper}
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.database.DBHelper
class ShadowsocksBackupAgent extends BackupAgentHelper {
// The names of the SharedPreferences groups that the application maintains. These
// are the same strings that are passed to getSharedPreferences(String, int).
val PREFS_DISPLAY = "com.github.shadowsocks_preferences"
// An arbitrary string used within the BackupAgentHelper implementation to
// identify the SharedPreferencesBackupHelper's data.
val MY_PREFS_BACKUP_KEY = "com.github.shadowsocks"
val DATABASE = "com.github.shadowsocks.database.profile"
override def onCreate() {
val helper = new SharedPreferencesBackupHelper(this, PREFS_DISPLAY)
addHelper(MY_PREFS_BACKUP_KEY, helper)
addHelper(DATABASE, new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE,
Acl.CUSTOM_RULES + ".acl"))
}
override def onCreate(): Unit = addHelper("com.github.shadowsocks.database.profile",
new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE, Acl.CUSTOM_RULES + ".acl"))
}
@@ -24,11 +24,12 @@ import java.io.File
import java.net.{Inet6Address, InetAddress}
import java.util.Locale
import android.app.Service
import android.content._
import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{AclSyncJob, Acl}
import com.github.shadowsocks.acl.{Acl, AclSyncJob}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
import eu.chainfire.libsuperuser.Shell
@@ -43,7 +44,6 @@ class ShadowsocksNatService extends BaseService {
val CMD_IPTABLES_DNAT_ADD_SOCKS =
"iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123"
private var notification: ShadowsocksNotification = _
val myUid: Int = android.os.Process.myUid()
var sslocalProcess: GuardedProcess = _
@@ -74,7 +74,7 @@ class ShadowsocksNatService extends BaseService {
"-t", "10",
"-b", "127.0.0.1",
"-l", (profile.localPort + 63).toString,
"-L", profile.remoteDns.trim + ":53",
"-L", profile.remoteDns.split(",").head.trim + ":53",
"-c", buildShadowsocksConfig("ss-tunnel-nat.conf"))
if (profile.udpdns) cmd.append("-u")
@@ -177,15 +177,17 @@ class ShadowsocksNatService extends BaseService {
su.addCommand((init_sb ++ http_sb).toArray)
}
override def startRunner(profile: Profile): Unit = if (su == null)
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) {
su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) =>
if (exitCode == 0) super.startRunner(profile) else {
if (exitCode == 0) super.onStartCommand(intent, flags, startId) else {
if (su != null) {
su.close()
su = null
}
super.stopRunner(stopService = true, getString(R.string.nat_no_root))
})
Service.START_NOT_STICKY
} else super.onStartCommand(intent, flags, startId)
override def connect() {
super.connect()
@@ -204,12 +206,11 @@ class ShadowsocksNatService extends BaseService {
AclSyncJob.schedule(profile.route)
changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, profile.getName, true)
}
override def stopRunner(stopService: Boolean, msg: String = null) {
override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true)
if (notification != null) notification.destroy()
override def stopRunner(stopService: Boolean, msg: String = null) {
// channge the state
changeState(State.STOPPING)
@@ -28,13 +28,15 @@ import android.os.{Build, PowerManager}
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationCompat.BigTextStyle
import android.support.v4.content.ContextCompat
import android.support.v4.os.BuildCompat
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub
import com.github.shadowsocks.utils.{Action, State, TrafficMonitor, Utils}
/**
* @author Mygod
*/
class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) {
class ShadowsocksNotification(private val service: BaseService, profileName: String,
channel: String, visible: Boolean = false) {
private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
private lazy val callback = new Stub {
@@ -52,7 +54,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
private var lockReceiver: BroadcastReceiver = _
private var callbackRegistered: Boolean = _
private val builder = new NotificationCompat.Builder(service)
private val builder = new NotificationCompat.Builder(service, channel)
.setWhen(0)
.setColor(ContextCompat.getColor(service, R.color.material_primary_500))
.setTicker(service.getString(R.string.forward_success))
@@ -26,6 +26,7 @@ import android.net.VpnService
import android.os.{Bundle, Handler}
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Utils
object ShadowsocksRunnerActivity {
private final val TAG = "ShadowsocksRunnerActivity"
@@ -46,7 +47,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
def startBackgroundService() {
if (app.isNatEnabled) {
bgService.use(app.profileId)
Utils.startSsService(this)
finish()
} else {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
@@ -73,7 +74,6 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
} else {
attachService()
}
finish()
}
override def onDestroy() {
@@ -87,10 +87,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
resultCode match {
case Activity.RESULT_OK =>
if (bgService != null) {
bgService.use(app.profileId)
}
case Activity.RESULT_OK => Utils.startSsService(this)
case _ =>
Log.e(TAG, "Failed to start VpnService")
}
@@ -1,57 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* This program is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*******************************************************************************/
package com.github.shadowsocks
import android.app.Service
import android.content.Intent
import android.net.VpnService
import android.os.{Handler, IBinder}
import com.github.shadowsocks.ShadowsocksApplication.app
class ShadowsocksRunnerService extends Service with ServiceBoundContext {
val handler = new Handler()
override def onBind(intent: Intent): IBinder = {
null
}
override def onServiceConnected() {
handler.postDelayed(() => {
if (bgService != null) {
if (app.isNatEnabled) startBackgroundService()
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
handler.postDelayed(() => stopSelf(), 3000)
}
}, 1000)
}
def startBackgroundService(): Unit = bgService.useSync(app.profileId)
override def onCreate() {
super.onCreate()
attachService()
}
override def onDestroy() {
super.onDestroy()
detachService()
}
}
@@ -23,6 +23,7 @@ package com.github.shadowsocks
import java.io.File
import java.util.Locale
import android.app.Service
import android.content._
import android.content.pm.PackageManager.NameNotFoundException
import android.net.VpnService
@@ -30,7 +31,6 @@ import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
import scala.collection.mutable.ArrayBuffer
@@ -42,7 +42,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
var conn: ParcelFileDescriptor = _
var vpnThread: ShadowsocksVpnThread = _
private var notification: ShadowsocksNotification = _
var sslocalProcess: GuardedProcess = _
var overtureProcess: GuardedProcess = _
@@ -69,8 +68,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
vpnThread = null
}
if (notification != null) notification.destroy()
// channge the state
changeState(State.STOPPING)
@@ -103,20 +100,19 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}
}
override def startRunner(profile: Profile) {
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
// ensure the VPNService is prepared
if (VpnService.prepare(this) != null) {
val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(i)
stopRunner(stopService = true)
return
}
super.startRunner(profile)
Service.START_NOT_STICKY
} else super.onStartCommand(intent, flags, startId)
}
override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-vpn")
override def connect() {
super.connect()
@@ -137,13 +133,11 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES)
AclSyncJob.schedule(profile.route)
notification = new ShadowsocksNotification(this, profile.getName)
}
/** Called when the activity is first created. */
def handleConnection() {
startShadowsocksDaemon()
if (!profile.udpdns) {
@@ -159,13 +153,12 @@ class ShadowsocksVpnService extends VpnService with BaseService {
def startShadowsocksDaemon() {
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so",
"-V",
"-u",
"-b", "127.0.0.1",
"-l", profile.localPort.toString,
"-t", "600",
"-c", buildShadowsocksConfig("ss-local-vpn.conf"))
if (profile.udpdns) cmd += "-u"
if (profile.route != Acl.ALL) {
cmd += "--acl"
cmd += Acl.getFile(profile.route).getAbsolutePath
@@ -190,7 +183,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
.setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24)
builder.addDnsServer(profile.remoteDns.trim)
builder.addDnsServer("8.8.8.8") // It's fake DNS for tun2socks, not the real remote DNS
if (profile.ipv6) {
builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126)
@@ -215,14 +208,17 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}
}
if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) {
if (profile.route == Acl.ALL
|| profile.route == Acl.BYPASS_CHN
|| profile.route == Acl.CUSTOM_RULES) {
builder.addRoute("0.0.0.0", 0)
} else {
getResources.getStringArray(R.array.bypass_private_route).foreach(cidr => {
val subnet = Subnet.fromString(cidr)
builder.addRoute(subnet.address.getHostAddress, subnet.prefixSize)
})
builder.addRoute(profile.remoteDns.trim, 32)
profile.remoteDns.split(",").map(dns => Utils.parseNumericAddress(dns.trim)).foreach(dns =>
builder.addRoute(dns, dns.getAddress.length << 3))
}
conn = builder.establish()
@@ -242,10 +238,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.ipv6)
cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2"))
if (profile.udpdns)
cmd += "--enable-udprelay"
else
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
cmd += "--enable-udprelay"
if (!profile.udpdns)
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1",
profile.localPort + 53))
tun2socksProcess = new GuardedProcess(cmd: _*).start(() => sendFd(fd))
@@ -42,7 +42,7 @@ class TaskerActivity extends AppCompatActivity {
typedArray.recycle()
}
private var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
private val text = itemView.findViewById[CheckedTextView](android.R.id.text1)
itemView.setOnClickListener(this)
def bindDefault() {
@@ -30,10 +30,17 @@ import com.github.shadowsocks.ShadowsocksApplication.app
class TaskerReceiver extends BroadcastReceiver {
override def onReceive(context: Context, intent: Intent) {
val settings = TaskerSettings.fromIntent(intent)
var changed = false
app.profileManager.getProfile(settings.profileId) match {
case Some(_) => app.switchProfile(settings.profileId)
case Some(_) => if (app.dataStore.profileId != settings.profileId) {
app.switchProfile(settings.profileId)
changed = true
}
case _ =>
}
if (settings.switchOn) Utils.startSsService(context) else Utils.stopSsService(context)
if (settings.switchOn) {
Utils.startSsService(context)
if (changed) Utils.reloadSsService(context)
} else Utils.stopSsService(context)
}
}
@@ -30,7 +30,7 @@ class ToolbarFragment extends Fragment {
override def onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState)
toolbar = view.findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar = view.findViewById[Toolbar](R.id.toolbar)
val activity = getActivity.asInstanceOf[MainActivity]
if (activity.crossfader == null) activity.drawer.setToolbar(activity, toolbar, true)
}
@@ -1,6 +1,6 @@
package com.github.shadowsocks.acl
import java.io.{File, FileNotFoundException}
import java.io.{File, FileNotFoundException, IOException}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.IOUtils
@@ -22,25 +22,10 @@ class Acl {
val bypassHostnames = new mutable.SortedList[String]()
val proxyHostnames = new mutable.SortedList[String]()
val subnets = new mutable.SortedList[Subnet]()
val urls = new mutable.SortedList[String]()
@DatabaseField
var bypass: Boolean = _
def getBypassHostnamesString: String = bypassHostnames.mkString("\n")
def getProxyHostnamesString: String = proxyHostnames.mkString("\n")
def getSubnetsString: String = subnets.mkString("\n")
def setBypassHostnamesString(value: String) {
bypassHostnames.clear()
bypassHostnames ++= value.split("\n")
}
def setProxyHostnamesString(value: String) {
proxyHostnames.clear()
proxyHostnames ++= value.split("\n")
}
def setSubnetsString(value: String) {
subnets.clear()
subnets ++= value.split("\n").map(Subnet.fromString)
}
def fromAcl(other: Acl): Acl = {
bypassHostnames.clear()
bypassHostnames ++= other.bypassHostnames
@@ -48,21 +33,28 @@ class Acl {
proxyHostnames ++= other.proxyHostnames
subnets.clear()
subnets ++= other.subnets
urls.clear()
urls ++= other.urls
bypass = other.bypass
this
}
def fromSource(value: Source, defaultBypass: Boolean = false): Acl = {
def fromSource(value: Source, defaultBypass: Boolean = true): Acl = {
bypassHostnames.clear()
proxyHostnames.clear()
this.subnets.clear()
this.urls.clear()
bypass = defaultBypass
lazy val bypassSubnets = new mutable.SortedList[Subnet]()
lazy val proxySubnets = new mutable.SortedList[Subnet]()
var hostnames: mutable.SortedList[String] = if (defaultBypass) proxyHostnames else bypassHostnames
var subnets: mutable.SortedList[Subnet] = if (defaultBypass) proxySubnets else bypassSubnets
var in_urls = false
for (line <- value.getLines()) (line.indexOf('#') match {
case -1 => line
case index => line.substring(0, index) // trim comments
case -1 => if (!in_urls) line else ""
case index =>
if (line.contains("URLS_BEGIN")) in_urls = true
if (line.contains("URLS_END")) in_urls = false
line.substring(0, index) // trim comments
}).trim match {
case "[outbound_block_list]" =>
hostnames = null
@@ -76,7 +68,8 @@ class Acl {
case "[reject_all]" | "[bypass_all]" => bypass = true
case "[accept_all]" | "[proxy_all]" => bypass = false
case input if subnets != null && input.nonEmpty => try subnets += Subnet.fromString(input) catch {
case _: IllegalArgumentException => hostnames += input
case _: IllegalArgumentException => if (input.startsWith("http://") || input.startsWith("https://"))
urls += input else hostnames += input
}
case _ =>
}
@@ -85,9 +78,24 @@ class Acl {
}
final def fromId(id: String): Acl = fromSource(Source.fromFile(Acl.getFile(id)))
override def toString: String = {
def getAclString(network: Boolean): String = {
val result = new StringBuilder()
result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
if (urls.nonEmpty) {
result.append("#URLS_BEGIN\n")
result.append(urls.mkString("\n"))
if (network) {
try {
urls.foreach((url: String) => result.append(Source.fromURL(url).mkString))
} catch {
case _: IOException => // ignore
}
}
result.append("#URLS_END\n")
}
if (result.isEmpty) {
result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
}
val (bypassList, proxyList) =
if (bypass) (bypassHostnames.toStream, subnets.toStream.map(_.toString) #::: proxyHostnames.toStream)
else (subnets.toStream.map(_.toString) #::: bypassHostnames.toStream, proxyHostnames.toStream)
@@ -104,6 +112,8 @@ class Acl {
result.toString
}
override def toString: String = getAclString(false)
def isValidCustomRules: Boolean = bypass && bypassHostnames.isEmpty
// Don't change: dummy fields for OrmLite interaction
@@ -134,9 +144,9 @@ object Acl {
try acl.fromId(CUSTOM_RULES) catch {
case _: FileNotFoundException =>
}
acl.bypass = true
acl.bypassHostnames.clear() // everything is bypassed
acl
}
def save(id: String, acl: Acl): Unit = IOUtils.writeString(getFile(id), acl.toString)
def save(id: String, acl: Acl, network: Boolean = false): Unit = {
IOUtils.writeString(getFile(id), acl.getAclString(network))
}
}
@@ -34,6 +34,7 @@ import scala.io.Source
*/
object AclSyncJob {
final val TAG = "AclSyncJob"
final val MAX_RESCHEDULE = 3
def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route)
.setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.DAYS.toMillis(28))
@@ -54,7 +55,7 @@ class AclSyncJob(route: String) extends Job {
} catch {
case e: IOException =>
e.printStackTrace()
Result.RESCHEDULE
if (params.getFailureCount < AclSyncJob.MAX_RESCHEDULE) Result.RESCHEDULE else Result.FAILURE
case e: Exception => // unknown failures, probably shouldn't retry
e.printStackTrace()
Result.FAILURE
@@ -46,8 +46,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
private def createAclRuleDialog(text: CharSequence = "") = {
val view = getActivity.getLayoutInflater.inflate(R.layout.dialog_acl_rule, null)
val templateSelector = view.findViewById(R.id.template_selector).asInstanceOf[Spinner]
val editText = view.findViewById(R.id.content).asInstanceOf[EditText]
val templateSelector = view.findViewById[Spinner](R.id.template_selector)
val editText = view.findViewById[EditText](R.id.content)
PATTERN_DOMAIN.findFirstMatchIn(text) match {
case Some(m) =>
templateSelector.setSelection(DOMAIN)
@@ -70,7 +70,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
private final class AclRuleViewHolder(view: View) extends RecyclerView.ViewHolder(view)
with View.OnClickListener with View.OnLongClickListener {
var item: AnyRef = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[TextView]
private val text = itemView.findViewById[TextView](android.R.id.text1)
itemView.setOnClickListener(this)
itemView.setOnLongClickListener(this)
itemView.setBackgroundResource(R.drawable.background_selectable)
@@ -252,12 +252,11 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
toolbar.setOnMenuItemClickListener(this)
selectionItem = toolbar.getMenu.findItem(R.id.selection)
selectionItem.setVisible(selectedItems.nonEmpty)
list = view.findViewById(R.id.list).asInstanceOf[RecyclerView]
list = view.findViewById(R.id.list)
list.setLayoutManager(new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false))
list.setItemAnimator(new DefaultItemAnimator)
list.setAdapter(adapter)
val fastScroller = view.findViewById(R.id.fastscroller).asInstanceOf[FastScroller]
fastScroller.setRecyclerView(list)
view.findViewById[FastScroller](R.id.fastscroller).setRecyclerView(list)
undoManager = new UndoSnackbarManager[AnyRef](getActivity.findViewById(R.id.snackbar), adapter.undo)
new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) {
override def getSwipeDirs(recyclerView: RecyclerView, viewHolder: ViewHolder): Int =
@@ -20,10 +20,14 @@
package com.github.shadowsocks.database
import android.content.Context
import java.nio.ByteBuffer
import android.content.{Context, SharedPreferences}
import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteDatabase
import android.preference.PreferenceManager
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
@@ -46,17 +50,20 @@ object DBHelper {
}
class DBHelper(val context: Context)
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 22) {
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 23) {
import DBHelper._
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
lazy val kvPairDao: Dao[KeyValuePair, String] = getDao(classOf[KeyValuePair])
def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
TableUtils.createTable(connectionSource, classOf[Profile])
TableUtils.createTable(connectionSource, classOf[KeyValuePair])
}
def recreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
TableUtils.dropTable(connectionSource, classOf[Profile], true)
TableUtils.dropTable(connectionSource, classOf[KeyValuePair], true)
onCreate(database, connectionSource)
}
@@ -132,6 +139,19 @@ class DBHelper(val context: Context)
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
}
if (oldVersion < 23) {
import KeyValuePair._
val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT,
ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.isNAT, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.isNAT, false)) 1 else 0).toByte).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.currentVersionCode, TYPE_INT,
ByteBuffer.allocate(4).putInt(-1).array()))
}
} catch {
case ex: Exception =>
app.track(ex)
@@ -0,0 +1,32 @@
package com.github.shadowsocks.database
import com.j256.ormlite.field.{DataType, DatabaseField}
/**
* @author Mygod
*/
object KeyValuePair {
val TYPE_UNINITIALIZED = 0
val TYPE_BOOLEAN = 1
val TYPE_FLOAT = 2
val TYPE_INT = 3
val TYPE_LONG = 4
val TYPE_STRING = 5
val TYPE_STRING_SET = 6
}
class KeyValuePair {
@DatabaseField(id = true)
var key: String = _
@DatabaseField
var valueType: Int = _
@DatabaseField(dataType = DataType.BYTE_ARRAY)
var value: Array[Byte] = _
def this(key: String, valueType: Int, value: Array[Byte]) = {
this()
this.key = key
this.valueType = valueType
this.value = value
}
}
@@ -22,11 +22,11 @@ package com.github.shadowsocks.database
import java.util.Locale
import android.content.SharedPreferences
import android.net.Uri
import android.os.Binder
import android.util.Base64
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key
import com.j256.ormlite.field.{DataType, DatabaseField}
@@ -98,7 +98,8 @@ class Profile {
.scheme("ss")
.encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH,
Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes,
Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort))
Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE),
if (host.contains(':')) '[' + host + ']' else host, remotePort))
val configuration = new PluginConfiguration(plugin)
if (configuration.selected.nonEmpty)
builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false))
@@ -107,37 +108,38 @@ class Profile {
}
override def toString: String = toUri.toString
def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor
.putString(Key.name, name)
.putString(Key.host, host)
.putInt(Key.localPort, localPort)
.putInt(Key.remotePort, remotePort)
.putString(Key.password, password)
.putString(Key.route, route)
.putString(Key.remoteDns, remoteDns)
.putString(Key.method, method)
.putBoolean(Key.proxyApps, proxyApps)
.putBoolean(Key.bypass, bypass)
.putBoolean(Key.udpdns, udpdns)
.putBoolean(Key.ipv6, ipv6)
.putString(Key.individual, individual)
.putString(Key.plugin, plugin)
.remove(Key.dirty)
def deserialize(pref: SharedPreferences) {
def serialize(store: OrmLitePreferenceDataStore) {
store.putString(Key.name, name)
store.putString(Key.host, host)
store.putInt(Key.localPort, localPort)
store.putInt(Key.remotePort, remotePort)
store.putString(Key.password, password)
store.putString(Key.route, route)
store.putString(Key.remoteDns, remoteDns)
store.putString(Key.method, method)
store.proxyApps = proxyApps
store.bypass = bypass
store.putBoolean(Key.udpdns, udpdns)
store.putBoolean(Key.ipv6, ipv6)
store.individual = individual
store.plugin = plugin
store.remove(Key.dirty)
}
def deserialize(store: OrmLitePreferenceDataStore) {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = pref.getString(Key.name, null)
host = pref.getString(Key.host, null)
localPort = pref.getInt(Key.localPort, 0)
remotePort = pref.getInt(Key.remotePort, 0)
password = pref.getString(Key.password, null)
method = pref.getString(Key.method, null)
route = pref.getString(Key.route, null)
remoteDns = pref.getString(Key.remoteDns, null)
proxyApps = pref.getBoolean(Key.proxyApps, false)
bypass = pref.getBoolean(Key.bypass, false)
udpdns = pref.getBoolean(Key.udpdns, false)
ipv6 = pref.getBoolean(Key.ipv6, false)
individual = pref.getString(Key.individual, null)
plugin = pref.getString(Key.plugin, null)
name = store.getString(Key.name, null)
host = store.getString(Key.host, null)
localPort = store.getInt(Key.localPort, 0)
remotePort = store.getInt(Key.remotePort, 0)
password = store.getString(Key.password, null)
method = store.getString(Key.method, null)
route = store.getString(Key.route, null)
remoteDns = store.getString(Key.remoteDns, null)
proxyApps = store.proxyApps
bypass = store.bypass
udpdns = store.getBoolean(Key.udpdns, false)
ipv6 = store.getBoolean(Key.ipv6, false)
individual = store.individual
plugin = store.plugin
}
}
@@ -46,9 +46,9 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment
private final class IconListViewHolder(val dialog: BottomSheetDialog, view: View) extends ViewHolder(view)
with View.OnClickListener with View.OnLongClickListener {
private var index: Int = _
private val text1 = view.findViewById(android.R.id.text1).asInstanceOf[TextView]
private val text2 = view.findViewById(android.R.id.text2).asInstanceOf[TextView]
private val icon = view.findViewById(android.R.id.icon).asInstanceOf[ImageView]
private val text1 = view.findViewById[TextView](android.R.id.text1)
private val text2 = view.findViewById[TextView](android.R.id.text2)
private val icon = view.findViewById[ImageView](android.R.id.icon)
view.setOnClickListener(this)
view.setOnLongClickListener(this)
@@ -0,0 +1,10 @@
package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore
/**
* @author Mygod
*/
trait OnPreferenceDataStoreChangeListener {
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String)
}
@@ -0,0 +1,116 @@
package com.github.shadowsocks.preference
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.util
import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.{DBHelper, KeyValuePair}
import com.github.shadowsocks.utils.Key
import scala.collection.JavaConversions._
/**
* @author Mygod
*/
//noinspection AccessorLikeMethodIsUnit
final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDataStore {
import KeyValuePair._
override def getBoolean(key: String, defValue: Boolean = false): Boolean = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_BOOLEAN) ByteBuffer.wrap(pair.value).get() != 0 else defValue
case _ => defValue
}
override def getFloat(key: String, defValue: Float): Float = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_FLOAT) ByteBuffer.wrap(pair.value).getFloat() else defValue
case _ => defValue
}
override def getInt(key: String, defValue: Int): Int = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_INT) ByteBuffer.wrap(pair.value).getInt() else defValue
case _ => defValue
}
override def getLong(key: String, defValue: Long): Long = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_LONG) ByteBuffer.wrap(pair.value).getLong() else defValue
case _ => defValue
}
override def getString(key: String, defValue: String = null): String = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_STRING) new String(pair.value) else defValue
case _ => defValue
}
override def getStringSet(key: String, defValue: util.Set[String]): util.Set[String] =
dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair => if (pair.valueType == TYPE_STRING_SET) {
val buffer = ByteBuffer.wrap(pair.value)
val result = new util.HashSet[String]()
while (buffer.hasRemaining) {
val chArr = new Array[Byte](buffer.getInt)
buffer.get(chArr)
result.add(new String(chArr))
}
result
} else defValue
case _ => defValue
}
override def putBoolean(key: String, value: Boolean) {
dbHelper.kvPairDao.createOrUpdate(
new KeyValuePair(key, TYPE_BOOLEAN, ByteBuffer.allocate(1).put((if (value) 1 else 0).toByte).array()))
fireChangeListener(key)
}
override def putFloat(key: String, value: Float) {
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_FLOAT, ByteBuffer.allocate(4).putFloat(value).array()))
fireChangeListener(key)
}
override def putInt(key: String, value: Int) {
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_INT, ByteBuffer.allocate(4).putInt(value).array()))
fireChangeListener(key)
}
override def putLong(key: String, value: Long) {
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_LONG, ByteBuffer.allocate(8).putLong(value).array()))
fireChangeListener(key)
}
override def putString(key: String, value: String) {
value match {
case null => remove(key)
case _ => dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_STRING, value.getBytes()))
}
fireChangeListener(key)
}
override def putStringSet(key: String, value: util.Set[String]) {
val stream = new ByteArrayOutputStream()
for (v <- value) {
stream.write(ByteBuffer.allocate(4).putInt(v.length).array())
stream.write(v.getBytes())
}
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_STRING_SET, stream.toByteArray))
fireChangeListener(key)
}
def remove(key: String): Int = dbHelper.kvPairDao.deleteById(key)
private var listeners: Set[OnPreferenceDataStoreChangeListener] = Set.empty
private def fireChangeListener(key: String) = listeners.foreach(_.onPreferenceDataStoreChanged(this, key))
def registerChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners += listener
def unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners -= listener
def profileId: Int = getInt(Key.id, 0)
def profileId_=(i: Int): Unit = putInt(Key.id, i)
def isNAT: Boolean = getBoolean(Key.isNAT)
def isNAT_=(value: Boolean): Unit = putBoolean(Key.isNAT, value)
def proxyApps: Boolean = getBoolean(Key.proxyApps)
def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value)
def bypass: Boolean = getBoolean(Key.bypass)
def bypass_=(value: Boolean): Unit = putBoolean(Key.bypass, value)
def individual: String = getString(Key.individual)
def individual_=(value: String): Unit = putString(Key.individual, value)
def plugin: String = getString(Key.plugin)
def plugin_=(value: String): Unit = putString(Key.plugin, value)
def dirty: Boolean = getBoolean(Key.dirty)
def dirty_=(value: Boolean): Unit = putBoolean(Key.dirty, value)
}
@@ -94,6 +94,7 @@ object State {
object Action {
final val SERVICE = "com.github.shadowsocks.SERVICE"
final val CLOSE = "com.github.shadowsocks.CLOSE"
final val RELOAD = "com.github.shadowsocks.RELOAD"
final val EXTRA_PROFILE_ID = "com.github.shadowsocks.EXTRA_PROFILE_ID"
}
@@ -20,22 +20,24 @@
package com.github.shadowsocks.utils
import java.net.URI
import android.net.Uri
import android.util.{Base64, Log}
import com.github.shadowsocks.database.Profile
object Parser {
val TAG = "ShadowParser"
private val pattern = "(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]".r
private val pattern = "(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]".r
private val userInfoPattern = "^(.+?):(.*)$".r
private val legacyPattern = "^((.+?):(.*)@(.+?):(\\d+?))$".r
private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".r
def findAll(data: CharSequence): Iterator[Profile] =
pattern.findAllMatchIn(if (data == null) "" else data).map(m => {
val uri = Uri.parse(m.matched)
uri.getUserInfo match {
case null => new String(Base64.decode(uri.getHost, Base64.NO_PADDING), "UTF-8") match {
case legacyPattern(_, method, password, host, port) => // legacy uri
case null => val str = new String(Base64.decode(uri.getHost, Base64.NO_PADDING), "UTF-8"); str match {
case legacyPattern(method, password, host, port) => // legacy uri
val profile = new Profile
profile.method = method.toLowerCase
profile.password = password
@@ -54,8 +56,12 @@ object Parser {
val profile = new Profile
profile.method = method
profile.password = password
profile.host = uri.getHost
profile.remotePort = uri.getPort
// bug in Android: https://code.google.com/p/android/issues/detail?id=192855
val javaURI = new URI(m.matched)
profile.host = javaURI.getHost
if (profile.host.headOption.contains('[') && profile.host.lastOption.contains(']'))
profile.host = profile.host.substring(1, profile.host.length - 1)
profile.remotePort = javaURI.getPort
profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.getFragment
profile
@@ -33,7 +33,7 @@ import android.view.View.MeasureSpec
import android.view.{Gravity, View, Window}
import android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService}
import org.xbill.DNS._
import scala.collection.JavaConversions._
@@ -80,7 +80,9 @@ object Utils {
def resolve(host: String, addrType: Int): Option[String] = {
try {
val lookup = new Lookup(host, addrType)
val resolver = new SimpleResolver("114.114.114.114")
val resolver = new SimpleResolver("208.67.220.220")
resolver.setTCP(true)
resolver.setPort(443)
resolver.setTimeout(5)
lookup.setResolver(resolver)
val result = lookup.run()
@@ -135,10 +137,11 @@ object Utils {
}
def startSsService(context: Context) {
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
context.startService(intent)
val intent =
new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService])
if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent)
}
def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD))
def stopSsService(context: Context) {
val intent = new Intent(Action.CLOSE)
context.sendBroadcast(intent)
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
function try () {
"$@" || exit -1
}
[ -z "$ANDROID_NDK_HOME" ] && ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
MIN_API=$1
TARGET=$DIR/../main/jni/overture
DEPS=$DIR/.deps
ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-arm
ANDROID_ARM64_TOOLCHAIN=$DEPS/android-toolchain-21-arm64
ANDROID_X86_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-x86
ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-clang
ANDROID_ARM_STRIP=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-strip
ANDROID_ARM64_CC=$ANDROID_ARM64_TOOLCHAIN/bin/aarch64-linux-android-clang
ANDROID_ARM64_STRIP=$ANDROID_ARM64_TOOLCHAIN/bin/aarch64-linux-android-strip
ANDROID_X86_CC=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-clang
ANDROID_X86_STRIP=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-strip
try mkdir -p $DEPS $TARGET/armeabi-v7a $TARGET/x86 $TARGET/arm64-v8a
if [ ! -d "$ANDROID_ARM_TOOLCHAIN" ]; then
echo "Make standalone toolchain for ARM arch"
$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm \
--api $MIN_API --install-dir $ANDROID_ARM_TOOLCHAIN
fi
if [ ! -d "$ANDROID_ARM64_TOOLCHAIN" ]; then
echo "Make standalone toolchain for ARM64 arch"
$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm64 \
--api 21 --install-dir $ANDROID_ARM64_TOOLCHAIN
fi
if [ ! -d "$ANDROID_X86_TOOLCHAIN" ]; then
echo "Make standalone toolchain for X86 arch"
$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch x86 \
--api $MIN_API --install-dir $ANDROID_X86_TOOLCHAIN
fi
if [ ! -d "$DIR/go/bin" ]; then
echo "Build the custom go"
pushd $DIR/go/src
try ./make.bash
popd
fi
export GOROOT=$DIR/go
export GOPATH=$DIR
export PATH=$GOROOT/bin:$GOPATH/bin:$PATH
echo "Get dependences for overture"
go get -u github.com/tools/godep
pushd $GOPATH/src/github.com/shadowsocks/overture/main
godep restore
echo "Cross compile overture for arm"
try env CGO_ENABLED=1 CC=$ANDROID_ARM_CC GOOS=android GOARCH=arm GOARM=7 go build -ldflags="-s -w"
try $ANDROID_ARM_STRIP main
try mv main $TARGET/armeabi-v7a/liboverture.so
echo "Cross compile overture for arm64"
try env CGO_ENABLED=1 CC=$ANDROID_ARM64_CC GOOS=android GOARCH=arm64 go build -ldflags="-s -w"
try $ANDROID_ARM64_STRIP main
try mv main $TARGET/arm64-v8a/liboverture.so
echo "Cross compile overture for x86"
try env CGO_ENABLED=1 CC=$ANDROID_X86_CC GOOS=android GOARCH=386 go build -ldflags="-s -w"
try $ANDROID_X86_STRIP main
try mv main $TARGET/x86/liboverture.so
popd
echo "Successfully build overture"
+3
View File
@@ -1,3 +1,6 @@
* 0.0.4:
* Enlarge text size of number pickers;
* Update support library version to 26.0.0.
* 0.0.3:
* Update support library version to 25.2.0.
* 0.0.2:
+1 -1
View File
@@ -36,7 +36,7 @@ First you need to add this library to your dependencies. This library is written
and it's most convenient to use it with SBT:
```scala
libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.2"
libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.4"
```
### Native binary configuration
+1 -1
View File
@@ -2,7 +2,7 @@ enablePlugins(AndroidLib)
android.useSupportVectors
name := "plugin"
version := "0.0.3"
version := "0.0.4"
pomExtra in Global := {
<url>https://github.com/shadowsocks/shadowsocks-android</url>
+2 -2
View File
@@ -2,10 +2,10 @@
package="com.github.shadowsocks.plugin">
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="25"/>
android:targetSdkVersion="26"/>
<application
android:theme="@style/Theme.Shadowsocks">
<meta-data android:name="com.github.shadowsocks.plugin.version"
android:value="0.0.2"/>
android:value="0.0.4"/>
</application>
</manifest>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"サーバー設定"</string>
<!-- feature category -->
<string name="feature_cat">"ファンクション設定"</string>
<!-- alert category -->
<string name="yes">"はい"</string>
<string name="no">"いいえ"</string>
<string name="unsaved_changes_prompt">"変更は適応されておりません、保存しますか?"</string>
<string name="apply">"適用"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"서버 설정"</string>
<!-- feature category -->
<string name="feature_cat">"기능 설정"</string>
</resources>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"Настройки Сервера"</string>
<!-- feature category -->
<string name="feature_cat">"Дополнительные Настройки"</string>
<!-- alert category -->
<string name="yes">"Да"</string>
<string name="no">"Нет"</string>
<string name="unsaved_changes_prompt">"Изменения не сохранены. Сохранить?"</string>
<string name="apply">"Применить"</string>
</resources>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"服务器设置"</string>
<!-- feature category -->
<string name="feature_cat">"功能设置"</string>
<!-- alert category -->
<string name="yes">"是"</string>
<string name="no">"否"</string>
<string name="unsaved_changes_prompt">"保存修改吗?"</string>
<string name="apply">"应用"</string>
</resources>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"伺服器設定"</string>
<!-- feature category -->
<string name="feature_cat">"功能設定"</string>
<!-- alert category -->
<string name="yes">"是"</string>
<string name="no">"否"</string>
<string name="unsaved_changes_prompt">"要儲存變更嗎?"</string>
<string name="apply">"套用"</string>
</resources>
+1 -1
View File
@@ -40,6 +40,6 @@
<item name="android:scrollbars">vertical</item>
</style>
<style name="NumberPickerStyle">
<item name="android:textSize">14sp</item>
<item name="android:textSize">18sp</item>
</style>
</resources>
@@ -60,7 +60,8 @@ class EditTextPreference(context: Context, attrs: AttributeSet = null) extends P
}
override def setText(text: String): Unit = {
val old = getText
super.setText(text)
notifyChanged()
if (old != text) notifyChanged()
}
}
@@ -36,7 +36,7 @@ class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
val oldParent = editText.getParent.asInstanceOf[ViewGroup]
if (oldParent eq view) return
if (oldParent != null) oldParent.removeView(editText)
val oldEdit = view.findViewById(android.R.id.edit)
val oldEdit = view.findViewById[View](android.R.id.edit)
if (oldEdit == null) return
val container = oldEdit.getParent.asInstanceOf[ViewGroup]
if (container == null) return
@@ -77,7 +77,7 @@ class PreferenceGroupAdapter(group: PreferenceGroup) extends Old(group) {
view.setBackground(background)
ViewCompat.setPaddingRelative(view, s, t, e, b)
}
val widgetFrame = view.findViewById(android.R.id.widget_frame).asInstanceOf[ViewGroup]
val widgetFrame = view.findViewById[ViewGroup](android.R.id.widget_frame)
if (widgetFrame != null) {
val widgetResId = fieldWidgetResId.get(pl).asInstanceOf[Int]
if (widgetResId != 0) inflater.inflate(widgetResId, widgetFrame) else widgetFrame.setVisibility(View.GONE)
+3 -1
View File
@@ -1,4 +1,4 @@
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.6")
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.8")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
@@ -7,3 +7,5 @@ addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") // fot sbt-0.13.5 or higher
addSbtPlugin("org.scala-android" % "sbt-android-gms" % "0.4")