diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index 03bba9c3..54343ba6 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -38,16 +38,16 @@
已连接到「%s」
无法连接远程服务器
停止服务
- 正在关闭...
+ 正在关闭……
后台服务启动失败:%s
关闭
本地端口号应大于1024
端口号不能为空
代理服务器地址不能为空
连接
- 正在初始化...
- 重置中...
- 正在加载...
+ 正在初始化……
+ 重置中……
+ 正在加载……
配置文件
设置选项
删除此配置文件「%s」?
@@ -55,13 +55,10 @@
关于
影梭(shadowsocks)%s
刷新 DNS 缓存
- 刷新中...
+ 刷新中……
二维码
添加配置文件
- 启用 NAT 模式.
- 禁用 NAT 模式.
-
UDP 转发
由远程服务器转发 UDP 协议的数据包
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 49211bb4..73ccaec1 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -8,6 +8,9 @@
Global Settings
Profile
Switch to another profile
+ NAT mode
+ Use NAT/VPN mode.
+ NAT mode is disabled without root.
Server Settings
@@ -55,10 +58,8 @@
Connected to "%s"
Failed to connect the remote server
Stop the service
- Shutting down...
+ Shutting down…
%s
- Turn on NAT mode.
- Turn off NAT mode.
Close
diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml
index b81b41ab..29e54dd2 100644
--- a/src/main/res/xml/pref_all.xml
+++ b/src/main/res/xml/pref_all.xml
@@ -7,7 +7,9 @@
-
+
{
- if (Utils.isLollipopOrAbove && bgService != null
- && (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
- val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)
- status.edit().putBoolean(Key.isNAT, natEnabled).apply()
- deattachService()
- attachService()
- if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState))
- Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG)
- .show()
- true
- } else {
- false
- }
- })
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
@@ -446,7 +430,8 @@ class Shadowsocks
def attachService() {
if (bgService == null) {
- val s = if (!isVpnEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
+ val s = if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService]
+ else classOf[ShadowsocksNatService]
val intent = new Intent(this, s)
intent.setAction(Action.SERVICE)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
@@ -526,6 +511,7 @@ class Shadowsocks
}
private def setPreferenceEnabled(enabled: Boolean) {
+ preferences.findPreference(Key.isNAT).setEnabled(enabled)
for (name <- Shadowsocks.PROXY_PREFS) {
val pref = preferences.findPreference(name)
if (pref != null) {
@@ -537,7 +523,7 @@ class Shadowsocks
if (pref != null) {
if (Seq(Key.isGlobalProxy, Key.proxyedApps)
.contains(name)) {
- pref.setEnabled(enabled && (Utils.isLollipopOrAbove || !isVpnEnabled))
+ pref.setEnabled(enabled && (Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
} else {
pref.setEnabled(enabled)
}
@@ -628,12 +614,6 @@ class Shadowsocks
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
}
- var isRoot: Option[Boolean] = None
- def isVpnEnabled: Boolean = {
- if (isRoot.isEmpty) isRoot = Some(Console.isRoot)
- !(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
- }
-
def serviceStop() {
if (bgService != null) bgService.stop()
}
@@ -666,7 +646,7 @@ class Shadowsocks
def serviceStart() {
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
- if (isVpnEnabled) {
+ if (ShadowsocksApplication.isVpnEnabled) {
changeSwitch(checked = false)
}
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
index 5d85788b..d507b88f 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
@@ -46,8 +46,8 @@ import android.app.Application
import android.content.pm.PackageManager
import android.preference.PreferenceManager
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
-import com.github.shadowsocks.utils.{Key, Utils}
-import com.google.android.gms.analytics.{HitBuilders, GoogleAnalytics}
+import com.github.shadowsocks.utils.{Console, Key, Utils}
+import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders}
import com.google.android.gms.common.api.ResultCallback
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
@@ -60,6 +60,9 @@ object ShadowsocksApplication {
lazy val settings = PreferenceManager.getDefaultSharedPreferences(instance)
lazy val profileManager = new ProfileManager(settings, dbHelper)
+ val isRoot = Console.isRoot
+ def isVpnEnabled = !(isRoot && settings.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
+
def getVersionName = try {
instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName
} catch {
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
index 6c2a3532..8d1643ca 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
@@ -39,12 +39,7 @@
package com.github.shadowsocks
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.PackageManager
-import android.preference.PreferenceManager
+import android.content.{BroadcastReceiver, Context, Intent}
import com.github.shadowsocks.utils._
class ShadowsocksReceiver extends BroadcastReceiver {
@@ -52,18 +47,8 @@ class ShadowsocksReceiver extends BroadcastReceiver {
val TAG = "Shadowsocks"
def onReceive(context: Context, intent: Intent) {
- val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
- val status = context.getSharedPreferences(Key.status, Context.MODE_PRIVATE)
-
- var versionName: String = null
- try {
- versionName = context.getPackageManager.getPackageInfo(context.getPackageName, 0).versionName
- } catch {
- case e: PackageManager.NameNotFoundException =>
- versionName = "NONE"
- }
- val isAutoConnect: Boolean = settings.getBoolean(Key.isAutoConnect, false)
- val isInstalled: Boolean = status.getBoolean(versionName, false)
+ val isAutoConnect: Boolean = ShadowsocksApplication.settings.getBoolean(Key.isAutoConnect, false)
+ val isInstalled: Boolean = ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)
if (isAutoConnect && isInstalled) {
if (Utils.isLollipopOrAbove) {
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
index 7b2d3817..137912f7 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
@@ -40,19 +40,15 @@
package com.github.shadowsocks
import android.app.{Activity, KeyguardManager}
-import android.os._
-import android.net.VpnService
import android.content._
+import android.net.VpnService
+import android.os._
import android.util.Log
-import android.preference.PreferenceManager
-import com.github.shadowsocks.utils._
import com.github.shadowsocks.aidl.IShadowsocksService
+import com.github.shadowsocks.utils._
class ShadowsocksRunnerActivity extends Activity {
- private lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
- private lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
-
val handler = new Handler()
val connection = new ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) {
@@ -65,18 +61,12 @@ class ShadowsocksRunnerActivity extends Activity {
}
// Variables
- var isRoot: Option[Boolean] = None
var bgService: IShadowsocksService = _
var receiver:BroadcastReceiver = _
- def isVpnEnabled: Boolean = {
- if (isRoot.isEmpty) isRoot = Some(Console.isRoot)
- !(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
- }
-
def startBackgroundService() {
- if (isVpnEnabled) {
+ if (ShadowsocksApplication.isVpnEnabled) {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
if (intent != null) {
startActivityForResult(intent, Shadowsocks.REQUEST_CONNECT)
@@ -84,14 +74,15 @@ class ShadowsocksRunnerActivity extends Activity {
onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null)
}
} else {
- bgService.start(ConfigUtils.load(settings))
+ bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
finish()
}
}
def attachService() {
if (bgService == null) {
- val s = if (!isVpnEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
+ val s = if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService]
+ else classOf[ShadowsocksNatService]
val intent = new Intent(this, s)
intent.setAction(Action.SERVICE)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
@@ -137,7 +128,7 @@ class ShadowsocksRunnerActivity extends Activity {
resultCode match {
case Activity.RESULT_OK =>
if (bgService != null) {
- bgService.start(ConfigUtils.load(settings))
+ bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
}
case _ =>
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
index e27168f2..8a271746 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
@@ -43,15 +43,10 @@ import android.app.Service
import android.content._
import android.net.VpnService
import android.os._
-import android.preference.PreferenceManager
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.utils._
class ShadowsocksRunnerService extends Service {
-
- private lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
- private lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
-
val handler = new Handler()
val connection = new ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) {
@@ -64,35 +59,29 @@ class ShadowsocksRunnerService extends Service {
}
// Variables
- var isRoot: Option[Boolean] = None
var bgService: IShadowsocksService = _
- def isVpnEnabled: Boolean = {
- if (isRoot.isEmpty) isRoot = Some(Console.isRoot)
- !(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
- }
-
override def onBind(intent: Intent): IBinder = {
null
}
def startBackgroundService() {
- if (isVpnEnabled) {
+ if (ShadowsocksApplication.isVpnEnabled) {
val intent = VpnService.prepare(ShadowsocksRunnerService.this)
if (intent == null) {
if (bgService != null) {
- bgService.start(ConfigUtils.load(settings))
+ bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
}
}
} else {
- bgService.start(ConfigUtils.load(settings))
+ bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
}
stopSelf()
}
def attachService() {
if (bgService == null) {
- val s = if (!isVpnEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
+ val s = if (!ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
val intent = new Intent(this, s)
intent.setAction(Action.SERVICE)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
index db746897..2b170f54 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
@@ -2,6 +2,7 @@ package com.github.shadowsocks
import android.os.Bundle
import android.preference.{Preference, PreferenceFragment}
+import com.github.shadowsocks.utils.Key
// TODO: Move related logic here
class ShadowsocksSettings extends PreferenceFragment {
@@ -10,6 +11,12 @@ class ShadowsocksSettings extends PreferenceFragment {
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_all)
+ findPreference(Key.isNAT).setOnPreferenceChangeListener((preference: Preference, newValue: Any) =>
+ if (ShadowsocksApplication.isRoot) activity.handler.post(() => {
+ activity.deattachService()
+ activity.attachService()
+ true
+ }) else false)
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "reset")
activity.recovery()
diff --git a/src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala
new file mode 100644
index 00000000..2bd38d27
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala
@@ -0,0 +1,27 @@
+package com.github.shadowsocks.preferences
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.preference.SwitchPreference
+import android.util.AttributeSet
+import com.github.shadowsocks.{R, ShadowsocksApplication}
+
+/**
+ * TODO: No animations?
+ * @author Mygod
+ */
+final class NatSwitchPreference(context: Context, attrs: AttributeSet) extends SwitchPreference(context, attrs) {
+ private var disabled = false
+
+ override def isEnabled = super.isEnabled && !disabled
+
+ override def onGetDefaultValue(a: TypedArray, index: Int) = {
+ val result = ShadowsocksApplication.isVpnEnabled
+ if (!ShadowsocksApplication.isRoot) {
+ disabled = true
+ setEnabled(false)
+ setSummary(R.string.nat_summary_no_root)
+ }
+ result.asInstanceOf[AnyRef]
+ }
+}
diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala
index e4a9de80..1b1f621e 100644
--- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala
@@ -66,7 +66,6 @@ object Key {
val proxied = "Proxyed"
val isNAT = "isNAT"
- val status = "status"
val proxyedApps = "proxyedApps"
val route = "route"