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"