Merge pull request #469 from Mygod/master
Persist package names in individual/proxied
This commit is contained in:
@@ -34,6 +34,8 @@
|
||||
<string name="auto_connect_summary">随系统启动后台服务</string>
|
||||
<string name="forward_success">后台服务已开始运行。</string>
|
||||
<string name="service_failed">无法连接远程服务器</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>
|
||||
@@ -52,14 +54,15 @@
|
||||
<string name="about">关于</string>
|
||||
<string name="about_title">影梭(shadowsocks)%s</string>
|
||||
<string name="flush_dnscache">刷新 DNS 缓存</string>
|
||||
<string name="flush_dnscache_summary_disabled">需要 ROOT 权限</string>
|
||||
<string name="flush_dnscache_summary">只需在 NAT 模式下使用</string>
|
||||
<string name="flush_dnscache_summary_root">只需在 NAT 模式下使用,需要 ROOT 权限</string>
|
||||
<string name="flush_dnscache_no_root">在没有 ROOT 权限的情况下无法刷新 DNS 缓存</string>
|
||||
<string name="flushing">刷新中……</string>
|
||||
<string name="qrcode">二维码</string>
|
||||
<string name="add_profile">添加配置文件</string>
|
||||
|
||||
<string name="nat">NAT 模式 (仅限调试)</string>
|
||||
<string name="nat_summary">从 VPN 模式切换为 NAT 模式</string>
|
||||
<string name="nat_summary_no_root">NAT 模式已禁用,需要 ROOT 权限</string>
|
||||
<string name="nat_summary">从 VPN 模式切换为 NAT 模式,需要 ROOT 权限</string>
|
||||
|
||||
<string name="udp_dns">UDP 转发</string>
|
||||
<string name="udp_dns_summary">由远程服务器转发 UDP 协议的数据包</string>
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
<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>
|
||||
<string name="nat_summary">Use NAT mode instead of VPN mode</string>
|
||||
<string name="nat_summary_no_root">NAT mode is disabled without root</string>
|
||||
<string name="nat_summary">Use NAT mode instead of VPN mode. Requires ROOT permission.</string>
|
||||
<string name="stat">Network Traffic</string>
|
||||
<string name="stat_summary">Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s</string>
|
||||
<string name="stat_profiles">\n%1$s↑\t%2$s↓</string>
|
||||
@@ -61,6 +60,8 @@
|
||||
<!-- notification category -->
|
||||
<string name="forward_success">Shadowsocks started.</string>
|
||||
<string name="service_failed">Failed to connect the remote server</string>
|
||||
<string name="nat_no_root">NAT mode requires ROOT permission</string>
|
||||
<string name="switch_to_vpn">Switch to VPN mode</string>
|
||||
<string name="stop">Stop the service</string>
|
||||
<string name="stopping">Shutting down…</string>
|
||||
<string name="vpn_error">%s</string>
|
||||
@@ -84,7 +85,9 @@
|
||||
<string name="about_title">Shadowsocks %s</string>
|
||||
<string name="qrcode">QR Code</string>
|
||||
<string name="flush_dnscache">Flush DNS cache</string>
|
||||
<string name="flush_dnscache_summary_disabled">Requires ROOT permission</string>
|
||||
<string name="flush_dnscache_summary">Only useful in NAT mode</string>
|
||||
<string name="flush_dnscache_summary_root">Only useful in NAT mode. Requires ROOT permission.</string>
|
||||
<string name="flush_dnscache_no_root">Flush DNS cache cannot proceed without ROOT permission</string>
|
||||
<string name="flushing">Flushing</string>
|
||||
<string name="add_profile">Add Profile</string>
|
||||
<string name="action_export">Export to Clipboard</string>
|
||||
|
||||
@@ -83,12 +83,14 @@
|
||||
android:summary="@string/auto_connect_summary"
|
||||
android:title="@string/auto_connect"/>
|
||||
|
||||
<com.github.shadowsocks.preferences.NatSwitchPreference android:key="isNAT"
|
||||
android:title="@string/nat"
|
||||
android:summary="@string/nat_summary"/>
|
||||
<SwitchPreference android:key="isNAT"
|
||||
android:title="@string/nat"
|
||||
android:summary="@string/nat_summary"/>
|
||||
<Preference android:key="recovery" android:title="@string/recovery"
|
||||
android:summary="@string/recovery_summary"/>
|
||||
<Preference android:key="flush_dnscache" android:title="@string/flush_dnscache"/>
|
||||
<Preference android:key="flush_dnscache"
|
||||
android:title="@string/flush_dnscache"
|
||||
android:summary="@string/flush_dnscache_summary_root"/>
|
||||
<Preference android:key="about" android:title="@string/about"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
@@ -58,18 +58,16 @@ import com.github.shadowsocks.utils.{Key, Utils}
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.language.implicitConversions
|
||||
|
||||
object AppManager {
|
||||
case class ProxiedApp(uid: Int, name: String, packageName: String, icon: Drawable)
|
||||
case class ProxiedApp(name: String, packageName: String, icon: Drawable)
|
||||
private case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
|
||||
|
||||
var cachedApps: Array[ProxiedApp] = _
|
||||
private def getApps(pm: PackageManager) = {
|
||||
if (cachedApps == null) cachedApps = pm.getInstalledApplications(0).filter(_.uid >= 10000)
|
||||
.map(a => new ProxiedApp(a.uid, pm.getApplicationLabel(a).toString, a.packageName, a.loadIcon(pm))).toArray
|
||||
if (cachedApps == null) cachedApps = pm.getInstalledApplications(0)
|
||||
.map(a => new ProxiedApp(pm.getApplicationLabel(a).toString, a.packageName, a.loadIcon(pm))).toArray
|
||||
cachedApps
|
||||
}
|
||||
}
|
||||
@@ -79,7 +77,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
import AppManager._
|
||||
|
||||
private var apps: Array[ProxiedApp] = _
|
||||
private var proxiedApps: mutable.HashSet[Int] = _
|
||||
private var proxiedApps: mutable.HashSet[String] = _
|
||||
private var toolbar: Toolbar = _
|
||||
private var appListView: ListView = _
|
||||
private var loadingView: View = _
|
||||
@@ -89,10 +87,10 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
|
||||
def loadApps() {
|
||||
appsLoading = true
|
||||
proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('|').map(_.toInt).to[mutable.HashSet]
|
||||
proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
|
||||
apps = getApps(getPackageManager).sortWith((a, b) => {
|
||||
val aProxied = proxiedApps.contains(a.uid)
|
||||
if (aProxied ^ proxiedApps.contains(b.uid)) aProxied else a.name < b.name
|
||||
val aProxied = proxiedApps.contains(a.packageName)
|
||||
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
|
||||
})
|
||||
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
|
||||
override def getView(position: Int, view: View, parent: ViewGroup): View = {
|
||||
@@ -116,19 +114,19 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
entry.icon.setImageDrawable(app.icon)
|
||||
val switch = entry.switch
|
||||
switch.setTag(app)
|
||||
switch.setChecked(proxiedApps.contains(app.uid))
|
||||
switch.setChecked(proxiedApps.contains(app.packageName))
|
||||
entry.text.setTag(switch)
|
||||
convertView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def setProxied(uid: Int, proxied: Boolean) = if (proxied) proxiedApps.add(uid) else proxiedApps.remove(uid)
|
||||
private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn)
|
||||
|
||||
/** Called an application is check/unchecked */
|
||||
def onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||
val app: ProxiedApp = buttonView.getTag.asInstanceOf[ProxiedApp]
|
||||
if (app != null) setProxied(app.uid, isChecked)
|
||||
if (app != null) setProxied(app.packageName, isChecked)
|
||||
saveAppSettings(this)
|
||||
}
|
||||
|
||||
@@ -136,8 +134,8 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
val switch = v.getTag.asInstanceOf[ListEntry].switch
|
||||
val app: ProxiedApp = switch.getTag.asInstanceOf[ProxiedApp]
|
||||
if (app != null) {
|
||||
val proxied = !proxiedApps.contains(app.uid)
|
||||
setProxied(app.uid, proxied)
|
||||
val proxied = !proxiedApps.contains(app.packageName)
|
||||
setProxied(app.packageName, proxied)
|
||||
switch.setChecked(proxied)
|
||||
}
|
||||
saveAppSettings(this)
|
||||
@@ -158,7 +156,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
case R.id.action_export =>
|
||||
val bypass = prefs.getBoolean(Key.isBypassApps, false)
|
||||
val proxiedAppString = prefs.getString(Key.proxied, "")
|
||||
val clip = ClipData.newPlainText(Key.proxied, bypass + " " + proxiedAppString)
|
||||
val clip = ClipData.newPlainText(Key.proxied, bypass + "\n" + proxiedAppString)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
|
||||
return true
|
||||
@@ -171,11 +169,12 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
if (proxiedAppSequence != null) {
|
||||
val proxiedAppString = proxiedAppSequence.toString
|
||||
if (!proxiedAppString.isEmpty) {
|
||||
val array = proxiedAppString.split(" ")
|
||||
val bypass = array(0).toBoolean
|
||||
val apps = if (array.size > 1) array(1) else ""
|
||||
prefs.edit.putBoolean(Key.isBypassApps, bypass).apply()
|
||||
prefs.edit.putString(Key.proxied, apps).apply()
|
||||
val editor = prefs.edit
|
||||
val i = proxiedAppString.indexOf('\n')
|
||||
if (i < 0)
|
||||
editor.putBoolean(Key.isBypassApps, proxiedAppString.toBoolean).putString(Key.proxied, "").apply()
|
||||
else editor.putBoolean(Key.isBypassApps, proxiedAppString.substring(0, i).toBoolean)
|
||||
.putString(Key.proxied, proxiedAppString.substring(i + 1)).apply()
|
||||
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
|
||||
// Restart activity
|
||||
appListView.setVisibility(View.GONE)
|
||||
@@ -234,7 +233,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
totalItemCount: Int) {
|
||||
if (visible) {
|
||||
val name = apps(firstVisibleItem).name
|
||||
overlay.setText(if (name != null && name.length > 1) name(0).toString else "*")
|
||||
overlay.setText(if (name != null && name.length > 1) name(0).toUpper.toString else "*")
|
||||
overlay.setVisibility(View.VISIBLE)
|
||||
}
|
||||
}
|
||||
@@ -249,7 +248,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
}
|
||||
|
||||
def loadAppsAsync() {
|
||||
Future {
|
||||
ThrowableFuture {
|
||||
while (!appsLoading) loadApps()
|
||||
appsLoading = false
|
||||
handler.post(() => {
|
||||
@@ -260,7 +259,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
}
|
||||
|
||||
def saveAppSettings(context: Context) {
|
||||
if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("|")).apply
|
||||
if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
|
||||
}
|
||||
|
||||
var handler: Handler = null
|
||||
|
||||
@@ -65,8 +65,6 @@ import com.github.shadowsocks.utils._
|
||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
object Typefaces {
|
||||
def get(c: Context, assetPath: String): Typeface = {
|
||||
@@ -182,8 +180,13 @@ class Shadowsocks
|
||||
handler.postDelayed(() => fabProgressCircle.hide(), 1000)
|
||||
fab.setEnabled(true)
|
||||
changeSwitch(checked = false)
|
||||
if (m != null) Snackbar.make(findViewById(android.R.id.content),
|
||||
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show
|
||||
if (m != null) {
|
||||
val snackbar = Snackbar.make(findViewById(android.R.id.content),
|
||||
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG)
|
||||
if (m == getString(R.string.nat_no_root)) snackbar.setAction(R.string.switch_to_vpn,
|
||||
(_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
|
||||
snackbar.show
|
||||
}
|
||||
setPreferenceEnabled(enabled = true)
|
||||
case State.STOPPING =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
@@ -215,18 +218,21 @@ class Shadowsocks
|
||||
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
|
||||
try {
|
||||
// Workaround that convert port(String) to port(Int)
|
||||
val oldLocalPort = ShadowsocksApplication.settings.getString("port", "-1")
|
||||
val oldRemotePort = ShadowsocksApplication.settings.getString("remotePort", "-1")
|
||||
val oldLocalPort = ShadowsocksApplication.settings.getString(Key.localPort, "")
|
||||
val oldRemotePort = ShadowsocksApplication.settings.getString(Key.remotePort, "")
|
||||
|
||||
if (oldLocalPort != "-1") {
|
||||
ShadowsocksApplication.settings.edit.putInt(Key.localPort, oldLocalPort.toInt).commit()
|
||||
if (oldLocalPort != "") {
|
||||
ShadowsocksApplication.settings.edit.putInt(Key.localPort, oldLocalPort.toInt).apply()
|
||||
}
|
||||
if (oldRemotePort != "-1") {
|
||||
ShadowsocksApplication.settings.edit.putInt(Key.remotePort, oldRemotePort.toInt).commit()
|
||||
if (oldRemotePort != "") {
|
||||
ShadowsocksApplication.settings.edit.putInt(Key.remotePort, oldRemotePort.toInt).apply()
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception => // Ignore
|
||||
}
|
||||
val oldProxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "")
|
||||
if (oldProxiedApps.contains('|')) ShadowsocksApplication.settings.edit
|
||||
.putString(Key.proxied, DBHelper.updateProxiedApps(this, oldProxiedApps)).apply()
|
||||
|
||||
recovery()
|
||||
}
|
||||
@@ -317,10 +323,10 @@ class Shadowsocks
|
||||
cmd.append("chmod 666 %s%s-vpn.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||
}
|
||||
|
||||
if (!ShadowsocksApplication.isVpnEnabled) {
|
||||
Console.runRootCommand(cmd.toArray)
|
||||
} else {
|
||||
if (ShadowsocksApplication.isVpnEnabled) {
|
||||
Console.runCommand(cmd.toArray)
|
||||
} else {
|
||||
Console.runRootCommand(cmd.toArray)
|
||||
}
|
||||
|
||||
cmd.clear()
|
||||
@@ -341,8 +347,7 @@ class Shadowsocks
|
||||
cmd.append("rm -f %s%s-vpn.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||
cmd.append("rm -f %s%s-vpn.conf".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||
}
|
||||
Console.runCommand(cmd.toArray)
|
||||
if (!ShadowsocksApplication.isVpnEnabled) {
|
||||
if (ShadowsocksApplication.isVpnEnabled) Console.runCommand(cmd.toArray) else {
|
||||
Console.runRootCommand(cmd.toArray)
|
||||
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
|
||||
}
|
||||
@@ -361,7 +366,7 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
def prepareStartService() {
|
||||
Future {
|
||||
ThrowableFuture {
|
||||
if (ShadowsocksApplication.isVpnEnabled) {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent != null) {
|
||||
@@ -534,18 +539,6 @@ class Shadowsocks
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
def copyToSystem() {
|
||||
val ab = new ArrayBuffer[String]
|
||||
ab.append("mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system")
|
||||
for (executable <- Shadowsocks.EXECUTABLES) {
|
||||
ab.append("cp %s%s /system/bin/".formatLocal(Locale.ENGLISH, Path.BASE, executable))
|
||||
ab.append("chmod 755 /system/bin/" + executable)
|
||||
ab.append("chown root:shell /system/bin/" + executable)
|
||||
}
|
||||
ab.append("mount -o ro,remount -t yaffs2 /dev/block/mtdblock3 /system")
|
||||
Console.runRootCommand(ab.toArray)
|
||||
}
|
||||
|
||||
def install() {
|
||||
copyAssets(System.getABI)
|
||||
|
||||
@@ -565,7 +558,7 @@ class Shadowsocks
|
||||
def recovery() {
|
||||
serviceStop()
|
||||
val h = showProgress(R.string.recovering)
|
||||
Future {
|
||||
ThrowableFuture {
|
||||
reset()
|
||||
h.sendEmptyMessage(0)
|
||||
}
|
||||
@@ -573,8 +566,9 @@ class Shadowsocks
|
||||
|
||||
def flushDnsCache() {
|
||||
val h = showProgress(R.string.flushing)
|
||||
Future {
|
||||
Utils.toggleAirplaneMode(getBaseContext)
|
||||
ThrowableFuture {
|
||||
if (!Utils.toggleAirplaneMode(getBaseContext)) h.post(() => Snackbar.make(findViewById(android.R.id.content),
|
||||
R.string.flush_dnscache_no_root, Snackbar.LENGTH_LONG).show)
|
||||
h.sendEmptyMessage(0)
|
||||
}
|
||||
}
|
||||
@@ -595,7 +589,7 @@ class Shadowsocks
|
||||
def checkText(key: String): Boolean = {
|
||||
val text = ShadowsocksApplication.settings.getString(key, "")
|
||||
if (text != null && text.length > 0) return true
|
||||
Snackbar.make(findViewById(android.R.id.content), getString(R.string.proxy_empty), Snackbar.LENGTH_LONG).show
|
||||
Snackbar.make(findViewById(android.R.id.content), R.string.proxy_empty, Snackbar.LENGTH_LONG).show
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ 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.{Console, Key, Utils}
|
||||
import com.github.shadowsocks.utils.{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,8 +60,7 @@ 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 isVpnEnabled = !settings.getBoolean(Key.isNAT, false)
|
||||
|
||||
def getVersionName = try {
|
||||
instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName
|
||||
|
||||
@@ -55,8 +55,6 @@ import com.github.shadowsocks.utils._
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
class ShadowsocksNatService extends BaseService {
|
||||
|
||||
@@ -144,7 +142,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
})
|
||||
Console.runRootCommand(cmdBuf.toArray)
|
||||
} else {
|
||||
Console.runRootCommand(Array("ndc resolver flushdefaultif", "ndc resolver flushif wlan0"))
|
||||
Console.runRootCommand("ndc resolver flushdefaultif", "ndc resolver flushif wlan0")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,20 +374,29 @@ class ShadowsocksNatService extends BaseService {
|
||||
http_sb.append(Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||
}
|
||||
if (config.isProxyApps) {
|
||||
for (uid <- config.proxiedAppString.split('|').distinct) {
|
||||
if (!config.isBypassApps) {
|
||||
http_sb.append((Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS).replace("-t nat", "-t nat -m owner --uid-owner " + uid))
|
||||
} else {
|
||||
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid))
|
||||
}
|
||||
val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap
|
||||
for (pn <- config.proxiedAppString.split('\n')) uidMap.get(pn) match {
|
||||
case Some(uid) =>
|
||||
if (!config.isBypassApps) {
|
||||
http_sb.append((Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||
.replace("-t nat", "-t nat -m owner --uid-owner " + uid))
|
||||
} else {
|
||||
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid))
|
||||
}
|
||||
case _ => // probably removed package, ignore
|
||||
}
|
||||
}
|
||||
Console.runRootCommand(init_sb.toArray)
|
||||
Console.runRootCommand(http_sb.toArray)
|
||||
Console.runRootCommand((init_sb ++ http_sb).toArray)
|
||||
}
|
||||
|
||||
override def startRunner(config: Config) {
|
||||
|
||||
changeState(State.CONNECTING)
|
||||
if (!Console.isRoot) {
|
||||
changeState(State.STOPPED, getString(R.string.nat_no_root))
|
||||
return
|
||||
}
|
||||
|
||||
super.startRunner(config)
|
||||
|
||||
// register close receiver
|
||||
@@ -404,10 +411,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
|
||||
ShadowsocksApplication.track(TAG, "start")
|
||||
|
||||
changeState(State.CONNECTING)
|
||||
|
||||
Future {
|
||||
|
||||
ThrowableFuture {
|
||||
if (config.proxy == "198.199.101.152") {
|
||||
val holder = ShadowsocksApplication.containerHolder
|
||||
try {
|
||||
@@ -465,7 +469,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
closeReceiver = null
|
||||
}
|
||||
|
||||
notification.destroy()
|
||||
if (notification != null) notification.destroy()
|
||||
|
||||
ShadowsocksApplication.track(TAG, "stop")
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.github.shadowsocks.utils.Key
|
||||
// TODO: Move related logic here
|
||||
class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener {
|
||||
private lazy val activity = getActivity.asInstanceOf[Shadowsocks]
|
||||
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
|
||||
|
||||
private var isProxyApps: SwitchPreference = _
|
||||
|
||||
@@ -35,10 +36,8 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
})
|
||||
|
||||
val flush = findPreference("flush_dnscache")
|
||||
if (Build.VERSION.SDK_INT >= 17 && !ShadowsocksApplication.isRoot) {
|
||||
flush.setEnabled(false)
|
||||
flush.setSummary(R.string.flush_dnscache_summary_disabled)
|
||||
} else flush.setOnPreferenceClickListener((preference: Preference) => {
|
||||
if (Build.VERSION.SDK_INT < 17) flush.setSummary(R.string.flush_dnscache_summary)
|
||||
flush.setOnPreferenceClickListener(_ => {
|
||||
ShadowsocksApplication.track(Shadowsocks.TAG, "flush_dnscache")
|
||||
activity.flushDnsCache()
|
||||
true
|
||||
@@ -73,12 +72,10 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
}
|
||||
|
||||
def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match {
|
||||
case Key.isNAT => if (ShadowsocksApplication.isRoot && activity != null) {
|
||||
activity.handler.post(() => {
|
||||
activity.deattachService
|
||||
activity.attachService
|
||||
})
|
||||
}
|
||||
case Key.isNAT => activity.handler.post(() => {
|
||||
activity.deattachService
|
||||
activity.attachService
|
||||
})
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ import org.apache.commons.net.util.SubnetUtils
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
val TAG = "ShadowsocksVpnService"
|
||||
@@ -122,7 +120,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
vpnThread = null
|
||||
}
|
||||
|
||||
notification.destroy()
|
||||
if (notification != null) notification.destroy()
|
||||
|
||||
// channge the state
|
||||
changeState(State.STOPPING)
|
||||
@@ -213,7 +211,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
changeState(State.CONNECTING)
|
||||
|
||||
Future {
|
||||
ThrowableFuture {
|
||||
if (config.proxy == "198.199.101.152") {
|
||||
val holder = ShadowsocksApplication.containerHolder
|
||||
try {
|
||||
@@ -390,7 +388,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
if (Utils.isLollipopOrAbove) {
|
||||
|
||||
if (config.isProxyApps) {
|
||||
for (pkg <- config.proxiedAppString.split('|').distinct) {
|
||||
for (pkg <- config.proxiedAppString.split('\n')) {
|
||||
if (!config.isBypassApps) {
|
||||
builder.addAllowedApplication(pkg)
|
||||
} else {
|
||||
|
||||
@@ -40,18 +40,30 @@
|
||||
package com.github.shadowsocks.database
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
|
||||
import com.j256.ormlite.dao.Dao
|
||||
import com.j256.ormlite.support.ConnectionSource
|
||||
import com.j256.ormlite.table.TableUtils
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
|
||||
object DBHelper {
|
||||
val PROFILE = "profile.db"
|
||||
final val PROFILE = "profile.db"
|
||||
private var apps: mutable.Buffer[ApplicationInfo] = _
|
||||
|
||||
def updateProxiedApps(context: Context, old: String) = {
|
||||
synchronized(if (apps == null) apps = context.getPackageManager.getInstalledApplications(0).asScala)
|
||||
val uidSet = old.split('|').map(_.toInt).toSet
|
||||
apps.filter(ai => uidSet.contains(ai.uid)).map(_.packageName).mkString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
class DBHelper(val context: Context)
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 13) {
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 14) {
|
||||
import DBHelper._
|
||||
|
||||
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
|
||||
|
||||
@@ -91,13 +103,17 @@ class DBHelper(val context: Context)
|
||||
" ipv6, individual FROM `tmp`;")
|
||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||
profileDao.executeRawNoArgs("COMMIT;")
|
||||
return
|
||||
}
|
||||
if (oldVersion < 13) {
|
||||
} else if (oldVersion < 13) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
|
||||
}
|
||||
if (oldVersion < 14) {
|
||||
for (profile <- profileDao.queryForAll.asScala) {
|
||||
profile.individual = updateProxiedApps(context, profile.individual)
|
||||
profileDao.update(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.github
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
package object shadowsocks {
|
||||
def ThrowableFuture[T](f: => T) = Future(f) onFailure {
|
||||
case e: Throwable => e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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 setEnabled(b: Boolean) = super.setEnabled(b && !disabled)
|
||||
|
||||
if (!ShadowsocksApplication.isRoot) {
|
||||
disabled = true
|
||||
setEnabled(false)
|
||||
setSummary(R.string.nat_summary_no_root)
|
||||
}
|
||||
|
||||
override def onGetDefaultValue(a: TypedArray, index: Int) =
|
||||
(!ShadowsocksApplication.isVpnEnabled).asInstanceOf[AnyRef]
|
||||
}
|
||||
@@ -75,17 +75,11 @@ object Console {
|
||||
shell.close()
|
||||
}
|
||||
|
||||
def runRootCommand(command: String): String = runRootCommand(Array(command))
|
||||
def runRootCommand(command: String, context: String): String = runRootCommand(Array(command), context)
|
||||
def runRootCommand(commands: Array[String]): String = runRootCommand(commands, "u:r:init_shell:s0")
|
||||
|
||||
def runRootCommand(commands: Array[String], context: String): String = {
|
||||
if (!isRoot) {
|
||||
return null
|
||||
}
|
||||
val shell = openRootShell(context)
|
||||
def runRootCommand(commands: String*): String = runRootCommand(commands.toArray)
|
||||
def runRootCommand(commands: Array[String]): String = {
|
||||
val shell = openRootShell("u:r:init_shell:s0")
|
||||
val sb = new StringBuilder
|
||||
shell.addCommand(commands, 0, ((commandCode: Int, exitCode: Int, output: util.List[String]) => {
|
||||
shell.addCommand(commands, 0, ((_, exitCode, output) => {
|
||||
if (exitCode < 0) {
|
||||
shell.close()
|
||||
} else {
|
||||
|
||||
@@ -140,12 +140,14 @@ object Utils {
|
||||
}
|
||||
|
||||
// Blocked > 3 seconds
|
||||
def toggleAirplaneMode(context: Context) {
|
||||
if (ShadowsocksApplication.isRoot) {
|
||||
Console.runRootCommand(Array("ndc resolver flushdefaultif", "ndc resolver flushif wlan0"))
|
||||
def toggleAirplaneMode(context: Context) = {
|
||||
if (Console.isRoot) {
|
||||
Console.runRootCommand("ndc resolver flushdefaultif", "ndc resolver flushif wlan0")
|
||||
true
|
||||
} else if (Build.VERSION.SDK_INT < 17) {
|
||||
toggleBelowApiLevel17(context)
|
||||
}
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
//noinspection ScalaDeprecation
|
||||
@@ -305,8 +307,7 @@ object Utils {
|
||||
var compatible: Boolean = false
|
||||
var version: Boolean = false
|
||||
|
||||
val command = Array(iptables + " --version", iptables + " -L -t nat -n")
|
||||
val lines = Console.runRootCommand(command)
|
||||
val lines = Console.runRootCommand(iptables + " --version", iptables + " -L -t nat -n")
|
||||
if (lines == null) return
|
||||
|
||||
if (lines.contains("OUTPUT")) {
|
||||
@@ -384,9 +385,7 @@ object Utils {
|
||||
hasRedirectSupport = 1
|
||||
|
||||
val command = Utils.getIptables + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154"
|
||||
val lines = Console.runRootCommand(command)
|
||||
|
||||
Console.runRootCommand(command.replace("-A", "-D"))
|
||||
val lines = Console.runRootCommand(command, command.replace("-A", "-D"))
|
||||
if (lines == null) return
|
||||
if (lines.contains("No chain/target/match")) {
|
||||
hasRedirectSupport = 0
|
||||
|
||||
Reference in New Issue
Block a user