Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0117daf6f | ||
|
|
b14ff7082d | ||
|
|
0e4cfb7a01 | ||
|
|
73996e2ff7 | ||
|
|
76f7306a93 | ||
|
|
09220ee335 | ||
|
|
6c992c5937 | ||
|
|
3010ed7a8b | ||
|
|
4fc56e2fea | ||
|
|
c92d66d9f3 | ||
|
|
b833cb6267 | ||
|
|
9c37f8cae6 | ||
|
|
16f4572839 | ||
|
|
44625ae5b0 | ||
|
|
159e1dff78 | ||
|
|
84ac2b06fa | ||
|
|
dcf190c1f5 | ||
|
|
74a0e397c1 |
@@ -5,10 +5,6 @@
|
||||
path = src/main/jni/badvpn
|
||||
url = https://github.com/shadowsocks/badvpn.git
|
||||
branch = shadowsocks-android
|
||||
[submodule "src/main/jni/iptables"]
|
||||
path = src/main/jni/iptables
|
||||
url = https://github.com/shadowsocks/iptables.git
|
||||
branch = shadowsocks-android
|
||||
[submodule "src/main/jni/libancillary"]
|
||||
path = src/main/jni/libancillary
|
||||
url = https://github.com/shadowsocks/libancillary.git
|
||||
|
||||
@@ -13,7 +13,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
|
||||
* JDK 1.8
|
||||
* SBT 0.13.0+
|
||||
* Android SDK r25+
|
||||
* Android NDK r10d+
|
||||
* Android NDK r12b+
|
||||
|
||||
### BUILD
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ useSupportVectors
|
||||
libraryDependencies ++= Seq(
|
||||
"dnsjava" % "dnsjava" % "2.1.7",
|
||||
"com.github.kevinsawicki" % "http-request" % "6.0",
|
||||
"eu.chainfire" % "libsuperuser" % "1.0.0.201602271131",
|
||||
"eu.chainfire" % "libsuperuser" % "1.0.0.201607041850",
|
||||
"net.glxn.qrgen" % "android" % "2.0",
|
||||
"com.google.android.gms" % "play-services-ads" % "9.2.0",
|
||||
"com.google.android.gms" % "play-services-analytics" % "9.2.0",
|
||||
@@ -49,14 +49,15 @@ libraryDependencies ++= Seq(
|
||||
|
||||
proguardVersion in Android := "5.2.1"
|
||||
|
||||
proguardOptions in Android ++= Seq("-keep class com.github.shadowsocks.**",
|
||||
"-keep class com.github.shadowsocks.System { *; }",
|
||||
"-keep class me.dm7.**",
|
||||
"-keep class com.google.android.gms.**",
|
||||
"-keepattributes *Annotation*",
|
||||
"-dontnote com.j256.ormlite.**",
|
||||
"-dontnote org.xbill.**",
|
||||
"-dontwarn org.xbill.**")
|
||||
proguardCache in Android := Seq()
|
||||
|
||||
proguardOptions in Android ++= Seq(
|
||||
"-keep class com.github.shadowsocks.System { *; }",
|
||||
"-dontnote com.google.android.gms.internal.**",
|
||||
"-dontnote com.j256.ormlite.**",
|
||||
"-dontnote me.dm7.barcodescannerview.**",
|
||||
"-dontnote org.xbill.**",
|
||||
"-dontwarn org.xbill.**")
|
||||
|
||||
lazy val nativeBuild = TaskKey[Unit]("native-build", "Build native executables")
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.6")
|
||||
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.7")
|
||||
|
||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"
|
||||
android:versionCode="139"
|
||||
android:versionName="2.10.6">
|
||||
android:versionCode="140"
|
||||
android:versionName="2.10.7">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
@@ -9,13 +9,14 @@
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen"
|
||||
android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera"
|
||||
android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.nfc"
|
||||
android:required="false"/>
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
android:required="false"/>
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
|
||||
@@ -9,4 +9,5 @@ interface IShadowsocksService {
|
||||
oneway void unregisterCallback(IShadowsocksServiceCallback cb);
|
||||
|
||||
oneway void use(in int profileId);
|
||||
void useSync(in int profileId);
|
||||
}
|
||||
|
||||
@@ -552,14 +552,5 @@ openssl_subdirs := $(addprefix $(LOCAL_PATH)/openssl/,$(addsuffix /Android.mk, \
|
||||
))
|
||||
include $(openssl_subdirs)
|
||||
|
||||
# Iptables
|
||||
# LOCAL_PATH := $(ROOT_PATH)
|
||||
# iptables_subdirs := $(addprefix $(LOCAL_PATH)/iptables/,$(addsuffix /Android.mk, \
|
||||
# iptables \
|
||||
# extensions \
|
||||
# libiptc \
|
||||
# ))
|
||||
# include $(iptables_subdirs)
|
||||
|
||||
# Import cpufeatures
|
||||
$(call import-module,android/cpufeatures)
|
||||
|
||||
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 460 B |
|
Before Width: | Height: | Size: 251 B After Width: | Height: | Size: 221 B |
|
Before Width: | Height: | Size: 480 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 175 B |
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 562 B |
|
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 257 B |
|
Before Width: | Height: | Size: 950 B After Width: | Height: | Size: 843 B |
|
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 436 B |
@@ -13,10 +13,6 @@
|
||||
<string name="stat_summary">Отправлено: \t\t\t\t\t%3$s\t↑\t%1$s/s\nПолучено: \t%4$s\t↓\t%2$s/s</string>
|
||||
<string name="stat_profiles">\n%1$s↑\t%2$s↓</string>
|
||||
<string name="traffic_summary">%1$s/s↑\t%2$s/s↓</string>
|
||||
<plurals name="bytes">
|
||||
<item quantity="one">Byte</item>
|
||||
<item quantity="other">Bytes</item>
|
||||
</plurals>
|
||||
<string name="connection_test_pending">Проверить подключение</string>
|
||||
<string name="connection_test_testing">Проверка…</string>
|
||||
<string name="connection_test_available">Успешно: %dms задержка</string>
|
||||
|
||||
@@ -82,9 +82,6 @@
|
||||
|
||||
<string name="stat">网络流量</string>
|
||||
<string name="stat_summary">发送: \t%3$s\t↑\t%1$s/s\n接收: \t%4$s\t↓\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>
|
||||
|
||||
@@ -83,9 +83,6 @@
|
||||
|
||||
<string name="stat">網路流量</string>
|
||||
<string name="stat_summary">傳送: \t%3$s\t↑\t%1$s/s\n接收: \t%4$s\t↓\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>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="DropDownPreference">
|
||||
<attr name="entries" format="reference" />
|
||||
<attr name="entryValues" format="reference" />
|
||||
<attr name="android:entries" />
|
||||
<attr name="android:entryValues" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="NumberPickerPreference">
|
||||
<attr name="max" format="integer" />
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<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>
|
||||
<string name="traffic_summary">%1$s/s↑\t%2$s/s↓</string>
|
||||
<plurals name="bytes">
|
||||
<plurals name="bytes" translatable="false">
|
||||
<item quantity="one">Byte</item>
|
||||
<item quantity="other">Bytes</item>
|
||||
</plurals>
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
<com.github.shadowsocks.preferences.DropDownPreference
|
||||
android:key="encMethod"
|
||||
android:persistent="false"
|
||||
app:entries="@array/enc_method_entry"
|
||||
app:entryValues="@array/enc_method_value"
|
||||
android:entries="@array/enc_method_entry"
|
||||
android:entryValues="@array/enc_method_value"
|
||||
android:summary="%s"
|
||||
android:title="@string/enc_method"/>
|
||||
<SwitchPreference
|
||||
@@ -55,8 +55,8 @@
|
||||
<com.github.shadowsocks.preferences.DropDownPreference
|
||||
android:key="route"
|
||||
android:persistent="false"
|
||||
app:entries="@array/route_entry"
|
||||
app:entryValues="@array/route_value"
|
||||
android:entries="@array/route_entry"
|
||||
android:entryValues="@array/route_value"
|
||||
android:summary="%s"
|
||||
android:title="@string/route_list"/>
|
||||
<SwitchPreference
|
||||
|
||||
@@ -158,23 +158,22 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
|
||||
|
||||
def onMenuItemClick(item: MenuItem): Boolean = {
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager]
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext)
|
||||
item.getItemId match {
|
||||
case R.id.action_apply_all =>
|
||||
app.profileManager.getAllProfiles match {
|
||||
case Some(profiles) =>
|
||||
val proxiedAppString = prefs.getString(Key.individual, "")
|
||||
profiles.foreach(profile => {
|
||||
profile.individual = proxiedAppString
|
||||
app.profileManager.updateProfile(profile)
|
||||
val proxiedAppString = profile.individual
|
||||
profiles.foreach(p => {
|
||||
p.individual = proxiedAppString
|
||||
app.profileManager.updateProfile(p)
|
||||
})
|
||||
Toast.makeText(this, R.string.action_apply_all, Toast.LENGTH_SHORT).show
|
||||
case _ => Toast.makeText(this, R.string.action_export_err, Toast.LENGTH_SHORT).show
|
||||
}
|
||||
return true
|
||||
case R.id.action_export =>
|
||||
val bypass = prefs.getBoolean(Key.bypass, false)
|
||||
val proxiedAppString = prefs.getString(Key.individual, "")
|
||||
val bypass = profile.bypass
|
||||
val proxiedAppString = profile.individual
|
||||
val clip = ClipData.newPlainText(Key.individual, bypass + "\n" + proxiedAppString)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
|
||||
@@ -185,13 +184,13 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
|
||||
if (proxiedAppSequence != null) {
|
||||
val proxiedAppString = proxiedAppSequence.toString
|
||||
if (!proxiedAppString.isEmpty) {
|
||||
val editor = prefs.edit
|
||||
val i = proxiedAppString.indexOf('\n')
|
||||
try {
|
||||
val (enabled, apps) = if (i < 0) (proxiedAppString, "")
|
||||
else (proxiedAppString.substring(0, i), proxiedAppString.substring(i + 1))
|
||||
bypassSwitch.setChecked(enabled.toBoolean)
|
||||
editor.putString(Key.individual, apps).apply()
|
||||
profile.individual = apps
|
||||
app.profileManager.updateProfile(profile)
|
||||
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
|
||||
appListView.setVisibility(View.GONE)
|
||||
loadingView.setVisibility(View.VISIBLE)
|
||||
|
||||
@@ -114,6 +114,8 @@ trait BaseService extends Service {
|
||||
case _ => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + state)
|
||||
}
|
||||
})
|
||||
|
||||
override def useSync(profileId: Int) = use(profileId)
|
||||
}
|
||||
|
||||
def checkProfile(profile: Profile) = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
|
||||
@@ -140,9 +142,7 @@ trait BaseService extends Service {
|
||||
profile.password = proxy(2).trim
|
||||
profile.method = proxy(3).trim
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
changeState(State.STOPPED, getString(R.string.service_failed))
|
||||
stopRunner(true)
|
||||
case ex: Exception => stopRunner(true, getString(R.string.service_failed))
|
||||
}
|
||||
|
||||
def startRunner(profile: Profile) {
|
||||
@@ -169,7 +169,7 @@ trait BaseService extends Service {
|
||||
Utils.ThrowableFuture(connect)
|
||||
}
|
||||
|
||||
def stopRunner(stopService: Boolean) {
|
||||
def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
// clean up recevier
|
||||
if (closeReceiverRegistered) {
|
||||
unregisterReceiver(closeReceiver)
|
||||
@@ -186,7 +186,7 @@ trait BaseService extends Service {
|
||||
}
|
||||
|
||||
// change the state
|
||||
changeState(State.STOPPED)
|
||||
changeState(State.STOPPED, msg)
|
||||
|
||||
// stop the service if nothing has bound to it
|
||||
if (stopService) stopSelf()
|
||||
@@ -242,7 +242,7 @@ trait BaseService extends Service {
|
||||
|
||||
protected def changeState(s: Int, msg: String = null) {
|
||||
val handler = new Handler(getMainLooper)
|
||||
handler.post(() => if (state != s) {
|
||||
handler.post(() => if (state != s || msg != null) {
|
||||
if (callbacksCount > 0) {
|
||||
val n = callbacks.beginBroadcast()
|
||||
for (i <- 0 until n) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.nio.charset.Charset
|
||||
|
||||
import android.app.Activity
|
||||
import android.content._
|
||||
import android.content.pm.PackageManager
|
||||
import android.nfc.NfcAdapter.CreateNdefMessageCallback
|
||||
import android.nfc.{NdefMessage, NdefRecord, NfcAdapter, NfcEvent}
|
||||
import android.os.{Bundle, Handler}
|
||||
@@ -263,6 +264,8 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
|
||||
val importAddFAB = findViewById(R.id.fab_import_add).asInstanceOf[FloatingActionButton]
|
||||
importAddFAB.setImageDrawable(dm.getDrawable(this, R.drawable.ic_content_paste))
|
||||
importAddFAB.setOnClickListener(this)
|
||||
menu.setOnMenuToggleListener(opened => if (opened) qrcodeAddFAB.setVisibility(
|
||||
if (getPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) View.VISIBLE else View.GONE))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.Manifest
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.app.NavUtils
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.ActionBar
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.{MenuItem, ViewGroup}
|
||||
import android.widget.Toast
|
||||
|
||||
import com.google.zxing.Result
|
||||
|
||||
import me.dm7.barcodescanner.core.IViewFinder
|
||||
import me.dm7.barcodescanner.core.ViewFinderView
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
|
||||
class ScannerActivity extends AppCompatActivity with ZXingScannerView.ResultHandler {
|
||||
|
||||
val MY_PERMISSIONS_REQUEST_CAMERA = 1
|
||||
|
||||
var scannerView: ZXingScannerView = null
|
||||
var scannerView: ZXingScannerView = _
|
||||
|
||||
override def onRequestPermissionsResult(requestCode: Int, permissions: Array[String],
|
||||
grantResults: Array[Int]) {
|
||||
|
||||
@@ -65,6 +65,7 @@ import com.github.shadowsocks.utils.CloseUtils._
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
@@ -110,7 +111,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
// Services
|
||||
private val callback = new IShadowsocksServiceCallback.Stub {
|
||||
def stateChanged(s: Int, m: String) {
|
||||
handler.post(() => if (state != s) {
|
||||
handler.post(() => {
|
||||
s match {
|
||||
case State.CONNECTING =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
@@ -286,34 +287,17 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
val cmd = new ArrayBuffer[String]()
|
||||
|
||||
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||
cmd.append("chmod 666 %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
cmd.append("chmod 666 %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
}
|
||||
|
||||
if (app.isNatEnabled) Console.runRootCommand(cmd.toArray) else Console.runCommand(cmd.toArray)
|
||||
|
||||
cmd.clear()
|
||||
|
||||
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||
try {
|
||||
val pid_nat = scala.io.Source.fromFile(getApplicationInfo.dataDir + "/" + task + "-nat.pid").mkString.trim.toInt
|
||||
val pid_vpn = scala.io.Source.fromFile(getApplicationInfo.dataDir + "/" + task + "-vpn.pid").mkString.trim.toInt
|
||||
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_nat))
|
||||
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_vpn))
|
||||
Process.killProcess(pid_nat)
|
||||
Process.killProcess(pid_vpn)
|
||||
} catch {
|
||||
case e: Throwable => // Ignore
|
||||
}
|
||||
cmd.append("rm -f %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
cmd.append("rm -f %s/%s-nat.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
cmd.append("rm -f %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
cmd.append("rm -f %s/%s-vpn.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
cmd.append("killall %s".formatLocal(Locale.ENGLISH, task))
|
||||
cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf"
|
||||
.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||
}
|
||||
if (app.isNatEnabled) {
|
||||
Console.runRootCommand(cmd.toArray)
|
||||
Console.runRootCommand(Utils.iptables + " -t nat -F OUTPUT")
|
||||
} else Console.runCommand(cmd.toArray)
|
||||
cmd.append("iptables -t nat -F OUTPUT")
|
||||
cmd.append("echo done")
|
||||
val result = Shell.SU.run(cmd.toArray)
|
||||
if (result != null && !result.isEmpty) return // fallback to SH
|
||||
}
|
||||
Shell.SH.run(cmd.toArray)
|
||||
}
|
||||
|
||||
def cancelStart() {
|
||||
@@ -535,7 +519,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
for (executable <- EXECUTABLES) {
|
||||
ab.append("chmod 755 " + getApplicationInfo.dataDir + "/" + executable)
|
||||
}
|
||||
Console.runCommand(ab.toArray)
|
||||
Shell.SH.run(ab.toArray)
|
||||
}
|
||||
|
||||
def recovery() {
|
||||
|
||||
@@ -45,12 +45,12 @@ import java.net.{Inet6Address, InetAddress}
|
||||
import java.util.Locale
|
||||
|
||||
import android.content._
|
||||
import android.net.{ConnectivityManager, Network}
|
||||
import android.os._
|
||||
import android.util.{Log, SparseArray}
|
||||
import com.github.shadowsocks.utils._
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.utils._
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
@@ -59,8 +59,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
|
||||
val TAG = "ShadowsocksNatService"
|
||||
|
||||
val CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN"
|
||||
val CMD_IPTABLES_DNAT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " +
|
||||
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 = _
|
||||
@@ -70,78 +69,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
var sstunnelProcess: Process = _
|
||||
var redsocksProcess: Process = _
|
||||
var pdnsdProcess: Process = _
|
||||
|
||||
private val dnsAddressCache = new SparseArray[String]
|
||||
|
||||
def getNetId(network: Network): Int = {
|
||||
network.getClass.getDeclaredField("netId").get(network).asInstanceOf[Int]
|
||||
}
|
||||
|
||||
def restoreDnsForAllNetwork() {
|
||||
val manager = getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager]
|
||||
val networks = manager.getAllNetworks
|
||||
val cmdBuf = new ArrayBuffer[String]()
|
||||
networks.foreach(network => {
|
||||
val netId = getNetId(network)
|
||||
val oldDns = dnsAddressCache.get(netId)
|
||||
if (oldDns != null) {
|
||||
cmdBuf.append("ndc resolver setnetdns %d \"\" %s".formatLocal(Locale.ENGLISH, netId, oldDns))
|
||||
dnsAddressCache.remove(netId)
|
||||
}
|
||||
})
|
||||
if (cmdBuf.nonEmpty) Console.runRootCommand(cmdBuf.toArray)
|
||||
}
|
||||
|
||||
def setDnsForAllNetwork(dns: String) {
|
||||
val manager = getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager]
|
||||
val networks = manager.getAllNetworks
|
||||
if (networks == null) return
|
||||
|
||||
val cmdBuf = new ArrayBuffer[String]()
|
||||
networks.foreach(network => {
|
||||
val networkInfo = manager.getNetworkInfo(network)
|
||||
if (networkInfo == null) return
|
||||
if (networkInfo.isConnected) {
|
||||
val netId = getNetId(network)
|
||||
val curDnsList = manager.getLinkProperties(network).getDnsServers
|
||||
if (curDnsList != null) {
|
||||
import scala.collection.JavaConverters._
|
||||
val curDns = curDnsList.asScala.map(ip => ip.getHostAddress).mkString(" ")
|
||||
if (curDns != dns) {
|
||||
dnsAddressCache.put(netId, curDns)
|
||||
cmdBuf.append("ndc resolver setnetdns %d \"\" %s".formatLocal(Locale.ENGLISH, netId, dns))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (cmdBuf.nonEmpty) Console.runRootCommand(cmdBuf.toArray)
|
||||
}
|
||||
|
||||
def setupDns() {
|
||||
setDnsForAllNetwork("127.0.0.1")
|
||||
}
|
||||
|
||||
def resetDns() = {
|
||||
restoreDnsForAllNetwork()
|
||||
}
|
||||
|
||||
def flushDns() {
|
||||
if (Utils.isLollipopOrAbove) {
|
||||
val manager = getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager]
|
||||
val networks = manager.getAllNetworks
|
||||
val cmdBuf = new ArrayBuffer[String]()
|
||||
networks.foreach(network => {
|
||||
val networkInfo = manager.getNetworkInfo(network)
|
||||
if (networkInfo.isAvailable) {
|
||||
val netId = network.getClass.getDeclaredField("netId").get(network).asInstanceOf[Int]
|
||||
cmdBuf.append("ndc resolver flushnet %d".formatLocal(Locale.ENGLISH, netId))
|
||||
}
|
||||
})
|
||||
Console.runRootCommand(cmdBuf.toArray)
|
||||
} else {
|
||||
Console.runRootCommand("ndc resolver flushdefaultif", "ndc resolver flushif wlan0")
|
||||
}
|
||||
}
|
||||
var su: Shell.Interactive = _
|
||||
|
||||
def startShadowsocksDaemon() {
|
||||
if (profile.route != Route.ALL) {
|
||||
@@ -264,7 +192,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
def handleConnection: Boolean = {
|
||||
def handleConnection {
|
||||
|
||||
startTunnel()
|
||||
if (!profile.udpdns) startDnsDaemon()
|
||||
@@ -272,7 +200,6 @@ class ShadowsocksNatService extends BaseService {
|
||||
startShadowsocksDaemon()
|
||||
setupIptables()
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
def onBind(intent: Intent): IBinder = {
|
||||
@@ -302,7 +229,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
pdnsdProcess = null
|
||||
}
|
||||
|
||||
Console.runRootCommand(Utils.iptables + " -t nat -F OUTPUT")
|
||||
su.addCommand("iptables -t nat -F OUTPUT")
|
||||
}
|
||||
|
||||
def setupIptables() = {
|
||||
@@ -310,9 +237,9 @@ class ShadowsocksNatService extends BaseService {
|
||||
val http_sb = new ArrayBuffer[String]
|
||||
|
||||
init_sb.append("ulimit -n 4096")
|
||||
init_sb.append(Utils.iptables + " -t nat -F OUTPUT")
|
||||
init_sb.append("iptables -t nat -F OUTPUT")
|
||||
|
||||
val cmd_bypass = Utils.iptables + CMD_IPTABLES_RETURN
|
||||
val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN"
|
||||
if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) {
|
||||
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host))
|
||||
}
|
||||
@@ -320,18 +247,17 @@ class ShadowsocksNatService extends BaseService {
|
||||
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid))
|
||||
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53"))
|
||||
|
||||
init_sb.append(Utils.iptables
|
||||
+ " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153")
|
||||
init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153")
|
||||
|
||||
if (!profile.proxyApps || profile.bypass) {
|
||||
http_sb.append(Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||
http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||
}
|
||||
if (profile.proxyApps) {
|
||||
val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap
|
||||
for (pn <- profile.individual.split('\n')) uidMap.get(pn) match {
|
||||
case Some(uid) =>
|
||||
if (!profile.bypass) {
|
||||
http_sb.append((Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||
http_sb.append(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))
|
||||
@@ -339,31 +265,38 @@ class ShadowsocksNatService extends BaseService {
|
||||
case _ => // probably removed package, ignore
|
||||
}
|
||||
}
|
||||
Console.runRootCommand((init_sb ++ http_sb).toArray)
|
||||
su.addCommand((init_sb ++ http_sb).toArray)
|
||||
}
|
||||
|
||||
override def startRunner(profile: Profile) {
|
||||
if (!Console.isRoot) {
|
||||
changeState(State.STOPPED, getString(R.string.nat_no_root))
|
||||
stopRunner(true)
|
||||
return
|
||||
override def startRunner(profile: Profile) = if (su == null) {
|
||||
su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) =>
|
||||
if (exitCode == 0) super.startRunner(profile) else {
|
||||
Log.wtf(TAG, "libsuperuser#55 has been fixed. Please remove the redundant code.")
|
||||
su.close()
|
||||
su = null
|
||||
super.stopRunner(true, getString(R.string.nat_no_root))
|
||||
})
|
||||
su.waitForIdle()
|
||||
if (!su.isRunning) {
|
||||
su.close()
|
||||
su = null
|
||||
super.stopRunner(true, getString(R.string.nat_no_root))
|
||||
}
|
||||
super.startRunner(profile)
|
||||
}
|
||||
|
||||
override def connect() {
|
||||
super.connect()
|
||||
|
||||
if (this.profile != null) {
|
||||
if (profile != null) {
|
||||
|
||||
// Clean up
|
||||
killProcesses()
|
||||
|
||||
var resolved: Boolean = false
|
||||
if (!Utils.isNumeric(this.profile.host)) {
|
||||
Utils.resolve(this.profile.host, enableIPv6 = true) match {
|
||||
var resolved = false
|
||||
if (!Utils.isNumeric(profile.host)) {
|
||||
Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(a) =>
|
||||
this.profile.host = a
|
||||
profile.host = a
|
||||
resolved = true
|
||||
case None => resolved = false
|
||||
}
|
||||
@@ -371,33 +304,31 @@ class ShadowsocksNatService extends BaseService {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
changeState(State.STOPPED, getString(R.string.invalid_server))
|
||||
stopRunner(true)
|
||||
} else if (handleConnection) {
|
||||
if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else {
|
||||
handleConnection
|
||||
// Set DNS
|
||||
flushDns()
|
||||
su.addCommand(Utils.FLUSH_DNS)
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name, true)
|
||||
} else {
|
||||
changeState(State.STOPPED, getString(R.string.service_failed))
|
||||
stopRunner(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def stopRunner(stopService: Boolean) {
|
||||
override def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
|
||||
if (notification != null) notification.destroy()
|
||||
|
||||
// channge the state
|
||||
changeState(State.STOPPING)
|
||||
|
||||
if (notification != null) notification.destroy()
|
||||
|
||||
app.track(TAG, "stop")
|
||||
|
||||
// reset NAT
|
||||
killProcesses()
|
||||
|
||||
super.stopRunner(stopService)
|
||||
su.close()
|
||||
su = null
|
||||
|
||||
super.stopRunner(stopService, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,13 +79,11 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
|
||||
|
||||
def setVisible(visible: Boolean, forceShow: Boolean = false) = if (isVisible != visible) {
|
||||
isVisible = visible
|
||||
builder.setPriority(if (visible) NotificationCompat.PRIORITY_DEFAULT else NotificationCompat.PRIORITY_MIN)
|
||||
builder.setPriority(if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN)
|
||||
show()
|
||||
} else if (forceShow) show()
|
||||
|
||||
def show() = {
|
||||
service.startForeground(1, builder.build)
|
||||
}
|
||||
def show() = service.startForeground(1, builder.build)
|
||||
|
||||
def destroy() {
|
||||
if (lockReceiver != null) {
|
||||
|
||||
@@ -43,7 +43,6 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import android.os.{IBinder, Handler}
|
||||
import com.github.shadowsocks.utils.Action
|
||||
import com.github.shadowsocks.utils.ConfigUtils
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
|
||||
@@ -55,29 +54,18 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
|
||||
}
|
||||
|
||||
override def onServiceConnected() {
|
||||
handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
|
||||
handler.postDelayed(() => if (bgService != null) {
|
||||
if (app.isNatEnabled) startBackgroundService()
|
||||
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
|
||||
stopSelf()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
def startBackgroundService() {
|
||||
if (app.isNatEnabled) bgService.use(app.profileId) else {
|
||||
val intent = VpnService.prepare(ShadowsocksRunnerService.this)
|
||||
if (intent == null) {
|
||||
if (bgService != null) {
|
||||
bgService.use(app.profileId)
|
||||
}
|
||||
}
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
def startBackgroundService() = bgService.useSync(app.profileId)
|
||||
|
||||
override def onCreate() {
|
||||
super.onCreate()
|
||||
attachService()
|
||||
|
||||
val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
|
||||
val intent = new Intent(this, s)
|
||||
intent.setAction(Action.SERVICE)
|
||||
startService(intent)
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
|
||||
@@ -82,7 +82,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
stopRunner(true)
|
||||
}
|
||||
|
||||
override def stopRunner(stopService: Boolean) {
|
||||
override def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
|
||||
if (vpnThread != null) {
|
||||
vpnThread.stopThread()
|
||||
@@ -105,7 +105,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
conn = null
|
||||
}
|
||||
|
||||
super.stopRunner(stopService)
|
||||
super.stopRunner(stopService, msg)
|
||||
}
|
||||
|
||||
def killProcesses() {
|
||||
@@ -165,10 +165,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
changeState(State.STOPPED, getString(R.string.invalid_server))
|
||||
stopRunner(true)
|
||||
} else if (handleConnection) {
|
||||
if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else if (handleConnection) {
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name)
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.os.Build
|
||||
import android.preference.Preference
|
||||
import android.support.annotation.NonNull
|
||||
import android.support.annotation.{ArrayRes, NonNull}
|
||||
import android.support.v7.widget.AppCompatSpinner
|
||||
import android.util.AttributeSet
|
||||
import android.view.{View, ViewGroup}
|
||||
@@ -23,14 +23,16 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe
|
||||
private val mSpinner = new AppCompatSpinner(mContext)
|
||||
private var mEntries: Array[CharSequence] = _
|
||||
private var mEntryValues: Array[CharSequence] = _
|
||||
private var mSelectedIndex: Int = _
|
||||
private var mValueSet: Boolean = _
|
||||
private var mSelectedIndex: Int = -1
|
||||
|
||||
mSpinner.setVisibility(View.INVISIBLE)
|
||||
mSpinner.setAdapter(mAdapter)
|
||||
mSpinner.setOnItemSelectedListener(new OnItemSelectedListener {
|
||||
override def onNothingSelected(parent: AdapterView[_]) { }
|
||||
override def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = setValueIndex(position)
|
||||
def onNothingSelected(parent: AdapterView[_]) { }
|
||||
def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = {
|
||||
val value = getValue(position)
|
||||
if (position != mSelectedIndex && callChangeListener(value)) setValue(position, value)
|
||||
}
|
||||
})
|
||||
setOnPreferenceClickListener((preference: Preference) => {
|
||||
// TODO: not working with scrolling
|
||||
@@ -39,8 +41,8 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe
|
||||
true
|
||||
})
|
||||
val a: TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference)
|
||||
setEntries(a.getTextArray(R.styleable.DropDownPreference_entries))
|
||||
mEntryValues = a.getTextArray(R.styleable.DropDownPreference_entryValues)
|
||||
setEntries(a.getTextArray(R.styleable.DropDownPreference_android_entries))
|
||||
setEntryValues(a.getTextArray(R.styleable.DropDownPreference_android_entryValues))
|
||||
a.recycle
|
||||
|
||||
protected override def getSummaryValue = getEntry
|
||||
@@ -62,7 +64,7 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe
|
||||
* @see #setEntries(CharSequence[])
|
||||
* @param entriesResId The entries array as a resource.
|
||||
*/
|
||||
def setEntries(entriesResId: Int): Unit = setEntries(getContext.getResources.getTextArray(entriesResId))
|
||||
def setEntries(@ArrayRes entriesResId: Int): Unit = setEntries(getContext.getResources.getTextArray(entriesResId))
|
||||
/**
|
||||
* The list of entries to be shown in the list in subsequent dialogs.
|
||||
*
|
||||
@@ -82,7 +84,7 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe
|
||||
* @see #setEntryValues(CharSequence[])
|
||||
* @param entryValuesResId The entry values array as a resource.
|
||||
*/
|
||||
def setEntryValues(entryValuesResId: Int): Unit =
|
||||
def setEntryValues(@ArrayRes entryValuesResId: Int): Unit =
|
||||
setEntryValues(getContext.getResources.getTextArray(entryValuesResId))
|
||||
/**
|
||||
* Returns the array of values to be saved for the preference.
|
||||
@@ -91,40 +93,26 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe
|
||||
*/
|
||||
def getEntryValues: Array[CharSequence] = mEntryValues
|
||||
|
||||
private def getValue(index: Int) = (if (mEntryValues == null) index else mEntryValues(index)).toString
|
||||
/**
|
||||
* Sets the value of the key. This should be one of the entries in [[getEntryValues]].
|
||||
*
|
||||
* @param value The value to set for the key.
|
||||
*/
|
||||
def setValue(value: String) {
|
||||
val i = findIndexOfValue(value)
|
||||
if (i > -1) setValueIndex(i)
|
||||
}
|
||||
def setValue(value: String): Unit = setValue(findIndexOfValue(value), value)
|
||||
|
||||
/**
|
||||
* Sets the value to the given index from the entry values.
|
||||
*
|
||||
* Remember to callChangeListener() if this extends Preference directly
|
||||
*
|
||||
* https://github.com/android/platform_frameworks_base/blob/
|
||||
* 4535e11fb7010f2b104d3f8b3954407b9f330e0f/core/java/
|
||||
* android/preference/ListPreference.java#L282
|
||||
*
|
||||
* @param index The index of the value to set.
|
||||
*/
|
||||
def setValueIndex(index: Int) {
|
||||
val changed = mSelectedIndex != index
|
||||
if (changed || !mValueSet) {
|
||||
mSelectedIndex = index
|
||||
mValueSet = true
|
||||
if (mEntryValues != null) {
|
||||
val value = mEntryValues(index).toString
|
||||
if (changed) callChangeListener(value)
|
||||
persistString(value)
|
||||
}
|
||||
if (changed) notifyChanged
|
||||
}
|
||||
def setValueIndex(index: Int) = setValue(index, getValue(index))
|
||||
|
||||
private def setValue(index: Int, value: String) {
|
||||
persistString(value)
|
||||
mSelectedIndex = index
|
||||
mSpinner.setSelection(index)
|
||||
notifyChanged
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +120,7 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe
|
||||
*
|
||||
* @return The value of the key.
|
||||
*/
|
||||
def getValue = if (mEntryValues == null) null else mEntryValues(mSelectedIndex).toString
|
||||
def getValue = if (mEntryValues == null || mSelectedIndex < 0) null else mEntryValues(mSelectedIndex).toString
|
||||
|
||||
/**
|
||||
* Returns the entry corresponding to the current value.
|
||||
|
||||
@@ -28,7 +28,7 @@ final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
|
||||
def getMin = if (picker == null) 0 else picker.getMinValue
|
||||
def getMax = picker.getMaxValue
|
||||
def setValue(i: Int) {
|
||||
if (i == getValue || !callChangeListener(i)) return
|
||||
if (i == getValue) return
|
||||
picker.setValue(i)
|
||||
value = picker.getValue
|
||||
persistInt(value)
|
||||
@@ -49,7 +49,14 @@ final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
|
||||
override protected def onDialogClosed(positiveResult: Boolean) {
|
||||
picker.clearFocus // commit changes
|
||||
super.onDialogClosed(positiveResult) // forward compatibility
|
||||
if (positiveResult) setValue(picker.getValue) else picker.setValue(value)
|
||||
if (positiveResult) {
|
||||
val value = picker.getValue
|
||||
if (callChangeListener(value)) {
|
||||
setValue(value)
|
||||
return
|
||||
}
|
||||
}
|
||||
picker.setValue(value)
|
||||
}
|
||||
override protected def onGetDefaultValue(a: TypedArray, index: Int) = a.getInt(index, getMin).asInstanceOf[AnyRef]
|
||||
override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Shadowsocks - A shadowsocks client for Android
|
||||
* Copyright (C) 2014 <max.c.lv@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* ___====-_ _-====___
|
||||
* _--^^^#####// \\#####^^^--_
|
||||
* _-^##########// ( ) \\##########^-_
|
||||
* -############// |\^^/| \\############-
|
||||
* _/############// (@::@) \\############\_
|
||||
* /#############(( \\// ))#############\
|
||||
* -###############\\ (oo) //###############-
|
||||
* -#################\\ / VV \ //#################-
|
||||
* -###################\\/ \//###################-
|
||||
* _#/|##########/\######( /\ )######/\##########|\#_
|
||||
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
|
||||
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
|
||||
* ` ` ` ` / | | | | \ ' ' ' '
|
||||
* ( | | | | )
|
||||
* __\ | | | | /__
|
||||
* (vvv(VVV)(VVV)vvv)
|
||||
*
|
||||
* HERE BE DRAGONS
|
||||
*
|
||||
*/
|
||||
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.util
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
import eu.chainfire.libsuperuser.Shell.{Builder, SU}
|
||||
|
||||
object Console {
|
||||
|
||||
private def openShell(): Shell.Interactive = {
|
||||
val builder = new Builder()
|
||||
builder
|
||||
.useSH()
|
||||
.setWatchdogTimeout(10)
|
||||
.open()
|
||||
}
|
||||
|
||||
private def openRootShell(context: String): Shell.Interactive = {
|
||||
val builder = new Builder()
|
||||
builder
|
||||
.setShell(SU.shell(0, context))
|
||||
.setWantSTDERR(true)
|
||||
.setWatchdogTimeout(10)
|
||||
.open()
|
||||
}
|
||||
|
||||
def runCommand(commands: Array[String]) {
|
||||
val shell = openShell()
|
||||
shell.addCommand(commands, 0, ((commandCode: Int, exitCode: Int, output: util.List[String]) =>
|
||||
if (exitCode < 0) shell.close()): Shell.OnCommandResultListener)
|
||||
shell.waitForIdle()
|
||||
shell.close()
|
||||
}
|
||||
|
||||
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, ((_, exitCode, output) => {
|
||||
if (exitCode < 0) {
|
||||
shell.close()
|
||||
} else {
|
||||
import scala.collection.JavaConversions._
|
||||
output.foreach(line => sb.append(line).append('\n'))
|
||||
}
|
||||
}): Shell.OnCommandResultListener)
|
||||
if (shell.waitForIdle()) {
|
||||
shell.close()
|
||||
sb.toString()
|
||||
}
|
||||
else {
|
||||
shell.close()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
def isRoot: Boolean = SU.available()
|
||||
}
|
||||
@@ -53,6 +53,7 @@ import android.view.{Gravity, View, Window}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
import org.xbill.DNS._
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
@@ -62,18 +63,6 @@ import scala.util.{Failure, Try}
|
||||
object Utils {
|
||||
private val TAG = "Shadowsocks"
|
||||
|
||||
/**
|
||||
* Check the system's iptables
|
||||
* Default to use the app's iptables
|
||||
*/
|
||||
private val DEFAULT_IPTABLES = "/system/bin/iptables"
|
||||
private val ALTERNATIVE_IPTABLES = "iptables"
|
||||
lazy val iptables = if (Console.isRoot) {
|
||||
val lines = Console.runRootCommand(DEFAULT_IPTABLES + " --version", DEFAULT_IPTABLES + " -L -t nat -n")
|
||||
if (Console.isRoot && lines != null && !lines.contains("OUTPUT") || !lines.contains("v1.4.")) ALTERNATIVE_IPTABLES
|
||||
else DEFAULT_IPTABLES
|
||||
} else DEFAULT_IPTABLES
|
||||
|
||||
def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
|
||||
def getSignature(context: Context): String = {
|
||||
@@ -129,12 +118,17 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// Because /sys/class/net/* isn't accessible since API level 24
|
||||
final val FLUSH_DNS = "for if in /sys/class/net/*; do " +
|
||||
"if [ \"down\" != $(cat $if/operstate) ]; then " + // up or unknown
|
||||
"ndc resolver flushif ${if##*/}; " +
|
||||
"fi " +
|
||||
"done; echo done"
|
||||
|
||||
// Blocked > 3 seconds
|
||||
def toggleAirplaneMode(context: Context) = {
|
||||
if (Console.isRoot) {
|
||||
Console.runRootCommand("ndc resolver flushdefaultif", "ndc resolver flushif wlan0")
|
||||
true
|
||||
} else if (Build.VERSION.SDK_INT < 17) {
|
||||
val result = Shell.SU.run(FLUSH_DNS)
|
||||
if (result != null && !result.isEmpty) true else if (Build.VERSION.SDK_INT < 17) {
|
||||
toggleBelowApiLevel17(context)
|
||||
true
|
||||
} else false
|
||||
|
||||