add vpn support (no root anymore for Android 4.0+)
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
/* Shadowsocks - A shadowsocks client for Android
|
||||
* Copyright (C) 2012 <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
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os._
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.util.Log
|
||||
import com.google.analytics.tracking.android.EasyTracker
|
||||
import java.io._
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.Inet6Address
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
import android.net.VpnService
|
||||
|
||||
object ShadowVpnService {
|
||||
def isServiceStarted: Boolean = {
|
||||
if (sRunningInstance == null) {
|
||||
false
|
||||
} else if (sRunningInstance.get == null) {
|
||||
sRunningInstance = null
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
var sRunningInstance: WeakReference[ShadowVpnService] = null
|
||||
|
||||
var sConn: ParcelFileDescriptor = null
|
||||
|
||||
}
|
||||
|
||||
class ShadowVpnService extends VpnService {
|
||||
|
||||
val TAG = "ShadowVpnService"
|
||||
val BASE = "/data/data/com.github.shadowsocks/"
|
||||
val SHADOWSOCKS_CONF = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"
|
||||
val MSG_CONNECT_START: Int = 0
|
||||
val MSG_CONNECT_FINISH: Int = 1
|
||||
val MSG_CONNECT_SUCCESS: Int = 2
|
||||
val MSG_CONNECT_FAIL: Int = 3
|
||||
val MSG_HOST_CHANGE: Int = 4
|
||||
val MSG_STOP_SELF: Int = 5
|
||||
val VPN_MTU = 1500
|
||||
|
||||
def getPid(name: String): Int = {
|
||||
try {
|
||||
val reader: BufferedReader = new BufferedReader(new FileReader(BASE + name + ".pid"))
|
||||
val line = reader.readLine
|
||||
return Integer.valueOf(line)
|
||||
}
|
||||
catch {
|
||||
case e: FileNotFoundException => {
|
||||
Log.e(TAG, "Cannot open pid file: " + name)
|
||||
}
|
||||
case e: IOException => {
|
||||
Log.e(TAG, "Cannot read pid file: " + name)
|
||||
}
|
||||
case e: NumberFormatException => {
|
||||
Log.e(TAG, "Invalid pid", e)
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
def startShadowsocksDaemon() {
|
||||
new Thread {
|
||||
override def run() {
|
||||
val cmd: String = (BASE + "shadowsocks -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f " + BASE + "shadowsocks.pid")
|
||||
.format(appHost, remotePort, localPort, sitekey, encMethod)
|
||||
System.exec(cmd)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
def getVersionName: String = {
|
||||
var version: String = null
|
||||
try {
|
||||
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
|
||||
version = pi.versionName
|
||||
}
|
||||
catch {
|
||||
case e: PackageManager.NameNotFoundException => {
|
||||
version = "Package name not found"
|
||||
}
|
||||
}
|
||||
version
|
||||
}
|
||||
|
||||
def handleCommand(intent: Intent) {
|
||||
if (intent == null) {
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
appHost = settings.getString("proxy", "127.0.0.1")
|
||||
sitekey = settings.getString("sitekey", "default")
|
||||
encMethod = settings.getString("encMethod", "table")
|
||||
try {
|
||||
remotePort = Integer.valueOf(settings.getString("remotePort", "1984"))
|
||||
}
|
||||
catch {
|
||||
case ex: NumberFormatException => {
|
||||
remotePort = 1984
|
||||
}
|
||||
}
|
||||
try {
|
||||
localPort = Integer.valueOf(settings.getString("port", "1984"))
|
||||
}
|
||||
catch {
|
||||
case ex: NumberFormatException => {
|
||||
localPort = 1984
|
||||
}
|
||||
}
|
||||
isGlobalProxy = settings.getBoolean("isGlobalProxy", false)
|
||||
isGFWList = settings.getBoolean("isGFWList", false)
|
||||
isBypassApps = settings.getBoolean("isBypassApps", false)
|
||||
isDNSProxy = settings.getBoolean("isDNSProxy", false)
|
||||
isHTTPProxy = settings.getBoolean("isHTTPProxy", false)
|
||||
if (isHTTPProxy) {
|
||||
localPort -= 1
|
||||
}
|
||||
new Thread(new Runnable {
|
||||
def run() {
|
||||
handler.sendEmptyMessage(MSG_CONNECT_START)
|
||||
var resolved: Boolean = false
|
||||
if (appHost != null) {
|
||||
var addr: InetAddress = null
|
||||
val isIPv6Support: Boolean = Utils.isIPv6Support
|
||||
|
||||
try {
|
||||
val addrs = InetAddress.getAllByName(appHost)
|
||||
for (a <- addrs) {
|
||||
if (isIPv6Support && addr == null && a.isInstanceOf[Inet6Address]) {
|
||||
addr = a
|
||||
}
|
||||
}
|
||||
if (addr == null) addr = addrs(0)
|
||||
} catch {
|
||||
case ignored: UnknownHostException => {
|
||||
addr = null
|
||||
}
|
||||
}
|
||||
if (addr != null) {
|
||||
appHost = addr.getHostAddress
|
||||
resolved = true
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "IPTABLES: " + Utils.getIptables)
|
||||
hasRedirectSupport = Utils.getHasRedirectSupport
|
||||
if (resolved && handleConnection) {
|
||||
notifyAlert(getString(R.string.forward_success), getString(R.string.service_running))
|
||||
handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500)
|
||||
}
|
||||
else {
|
||||
notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
|
||||
stopSelf()
|
||||
handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500)
|
||||
}
|
||||
handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500)
|
||||
}
|
||||
}).start()
|
||||
markServiceStarted()
|
||||
}
|
||||
|
||||
def waitForProcess(name: String): Boolean = {
|
||||
val pid: Int = getPid(name)
|
||||
if (pid == -1) return false
|
||||
Exec.hangupProcessGroup(-pid)
|
||||
val t: Thread = new Thread {
|
||||
override def run() {
|
||||
Exec.waitFor(-pid)
|
||||
Log.d(TAG, "Successfully exit pid: " + pid)
|
||||
}
|
||||
}
|
||||
t.start()
|
||||
try {
|
||||
t.join(300)
|
||||
}
|
||||
catch {
|
||||
case ignored: InterruptedException => {
|
||||
}
|
||||
}
|
||||
!t.isAlive
|
||||
}
|
||||
|
||||
def startVpn() {
|
||||
|
||||
val prefix = appHost.substring(0, appHost.indexOf('.')).toInt
|
||||
val builder = new Builder()
|
||||
builder
|
||||
.setSession(getString(R.string.app_name))
|
||||
.setMtu(VPN_MTU)
|
||||
.addAddress("172.16.0.1", 24)
|
||||
.addDnsServer("8.8.8.8")
|
||||
|
||||
for (i <- 1 to 254) {
|
||||
if (i != prefix && i != 127
|
||||
&& i != 172 && i != 192 && i != 10) builder.addRoute(i + ".0.0.0", 8)
|
||||
}
|
||||
|
||||
ShadowVpnService.sConn = builder.establish()
|
||||
if (ShadowVpnService.sConn == null) {
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
|
||||
val fd = ShadowVpnService.sConn.getFd
|
||||
|
||||
val cmd = (BASE + "tun2socks --netif-ipaddr 172.16.0.2 --udpgw-remote-server-addr 158.255.208.201:7300 " +
|
||||
"--netif-netmask 255.255.255.0 --socks-server-addr 127.0.0.1:%d --tunfd %d --tunmtu %d --pid " + BASE + "tun2socks.pid")
|
||||
.format(localPort, fd, VPN_MTU)
|
||||
|
||||
Log.d(TAG, cmd)
|
||||
System.exec(cmd)
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
def handleConnection: Boolean = {
|
||||
startShadowsocksDaemon()
|
||||
startVpn()
|
||||
true
|
||||
}
|
||||
|
||||
def initSoundVibrateLights(notification: Notification) {
|
||||
notification.sound = null
|
||||
notification.defaults |= Notification.DEFAULT_LIGHTS
|
||||
}
|
||||
|
||||
def markServiceStarted() {
|
||||
ShadowVpnService.sRunningInstance = new WeakReference[ShadowVpnService](this)
|
||||
}
|
||||
|
||||
def markServiceStopped() {
|
||||
ShadowVpnService.sRunningInstance = null
|
||||
}
|
||||
|
||||
def notifyAlert(title: String, info: String) {
|
||||
val openIntent: Intent = new Intent(this, classOf[Shadowsocks])
|
||||
openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
val contentIntent: PendingIntent = PendingIntent.getActivity(this, 0, openIntent, 0)
|
||||
val closeIntent: Intent = new Intent(Utils.CLOSE_ACTION)
|
||||
val actionIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, closeIntent, 0)
|
||||
val builder: NotificationCompat.Builder = new NotificationCompat.Builder(this)
|
||||
builder.setSmallIcon(R.drawable.ic_stat_shadowsocks).setWhen(0).setTicker(title).setContentTitle(getString(R.string.app_name)).setContentText(info).setContentIntent(contentIntent).addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.stop), actionIntent)
|
||||
}
|
||||
|
||||
override def onBind(intent: Intent): IBinder = {
|
||||
val action = intent.getAction
|
||||
if (VpnService.SERVICE_INTERFACE == action) {
|
||||
return super.onBind(intent)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override def onCreate() {
|
||||
super.onCreate()
|
||||
EasyTracker.getTracker.sendEvent("service", "start", getVersionName, 0L)
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
}
|
||||
|
||||
/** Called when the activity is closed. */
|
||||
override def onDestroy() {
|
||||
EasyTracker.getTracker.sendEvent("service", "stop", getVersionName, 0L)
|
||||
new Thread {
|
||||
override def run() {
|
||||
onDisconnect()
|
||||
}
|
||||
}.start()
|
||||
val ed: SharedPreferences.Editor = settings.edit
|
||||
ed.putBoolean("isRunning", false)
|
||||
ed.putBoolean("isConnecting", false)
|
||||
ed.commit
|
||||
super.onDestroy()
|
||||
markServiceStopped()
|
||||
}
|
||||
|
||||
def onDisconnect() {
|
||||
Utils.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
|
||||
val sb = new StringBuilder
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n")
|
||||
if (!waitForProcess("shadowsocks")) {
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n")
|
||||
}
|
||||
if (!waitForProcess("tun2socks")) {
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/tun2socks.pid`").append("\n")
|
||||
}
|
||||
Utils.runRootCommand(sb.toString())
|
||||
}
|
||||
|
||||
override def onStart(intent: Intent, startId: Int) {
|
||||
handleCommand(intent)
|
||||
}
|
||||
|
||||
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
|
||||
handleCommand(intent)
|
||||
Service.START_STICKY
|
||||
}
|
||||
|
||||
override def onRevoke() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
val handler: Handler = new Handler {
|
||||
override def handleMessage(msg: Message) {
|
||||
val ed: SharedPreferences.Editor = settings.edit
|
||||
msg.what match {
|
||||
case MSG_CONNECT_START =>
|
||||
ed.putBoolean("isConnecting", true)
|
||||
val pm: PowerManager = getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager]
|
||||
case MSG_CONNECT_FINISH =>
|
||||
ed.putBoolean("isConnecting", false)
|
||||
case MSG_CONNECT_SUCCESS =>
|
||||
ed.putBoolean("isRunning", true)
|
||||
case MSG_CONNECT_FAIL =>
|
||||
ed.putBoolean("isRunning", false)
|
||||
case MSG_HOST_CHANGE =>
|
||||
ed.putString("appHost", appHost)
|
||||
case MSG_STOP_SELF =>
|
||||
stopSelf()
|
||||
}
|
||||
ed.commit
|
||||
super.handleMessage(msg)
|
||||
}
|
||||
}
|
||||
var appHost: String = null
|
||||
var remotePort: Int = 0
|
||||
var localPort: Int = 0
|
||||
var sitekey: String = null
|
||||
var settings: SharedPreferences = null
|
||||
var hasRedirectSupport: Boolean = true
|
||||
var isGlobalProxy: Boolean = false
|
||||
var isGFWList: Boolean = false
|
||||
var isBypassApps: Boolean = false
|
||||
var isDNSProxy: Boolean = false
|
||||
var isHTTPProxy: Boolean = false
|
||||
var encMethod: String = null
|
||||
var apps: Array[ProxiedApp] = null
|
||||
}
|
||||
@@ -37,8 +37,7 @@
|
||||
*/
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.ProgressDialog
|
||||
import android.app.{Activity, AlertDialog, ProgressDialog}
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
@@ -46,9 +45,7 @@ import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.content.res.AssetManager
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Message
|
||||
import android.os.{Build, Bundle, Handler, Message}
|
||||
import android.preference.Preference
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
@@ -70,12 +67,14 @@ import net.saik0.android.unifiedpreference.UnifiedPreferenceFragment
|
||||
import net.saik0.android.unifiedpreference.UnifiedSherlockPreferenceActivity
|
||||
import org.jraf.android.backport.switchwidget.Switch
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.VpnService
|
||||
|
||||
object Shadowsocks {
|
||||
val PREFS_NAME = "Shadowsocks"
|
||||
val PROXY_PREFS = Array("proxy", "remotePort", "port", "sitekey", "encMethod")
|
||||
val FEATRUE_PREFS = Array("isHTTPProxy", "isDNSProxy", "isGFWList", "isGlobalProxy", "isBypassApps", "proxyedApps", "isAutoConnect")
|
||||
val TAG = "Shadowsocks"
|
||||
val REQUEST_CONNECT = 1
|
||||
|
||||
class ProxyFragment extends UnifiedPreferenceFragment with OnSharedPreferenceChangeListener {
|
||||
private def setPreferenceEnabled() {
|
||||
@@ -231,10 +230,12 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n")
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n")
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n")
|
||||
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/tun2socks.pid`").append("\n")
|
||||
sb.append("killall -9 pdnsd").append("\n")
|
||||
sb.append("killall -9 redsocks").append("\n")
|
||||
sb.append("killall -9 shadowsocks").append("\n")
|
||||
sb.append("killall -9 polipo").append("\n")
|
||||
sb.append("killall -9 tun2socks").append("\n")
|
||||
Utils.runRootCommand(sb.toString())
|
||||
}
|
||||
|
||||
@@ -246,12 +247,26 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
false
|
||||
}
|
||||
|
||||
def onCheckedChanged(compoundButton: CompoundButton, b: Boolean) {
|
||||
def onCheckedChanged(compoundButton: CompoundButton, checked: Boolean) {
|
||||
if (compoundButton eq switchButton) {
|
||||
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this)
|
||||
settings.edit.putBoolean("isRunning", true).commit
|
||||
if (!serviceStart) {
|
||||
settings.edit.putBoolean("isRunning", false).commit
|
||||
checked match {
|
||||
case true => {
|
||||
if (isVpnEnabled) {
|
||||
if (!serviceStart) {
|
||||
switchButton.setChecked(false)
|
||||
}
|
||||
} else {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent != null) {
|
||||
startActivityForResult(intent, Shadowsocks.REQUEST_CONNECT)
|
||||
} else {
|
||||
onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
case false => {
|
||||
serviceStop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,15 +321,6 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
true
|
||||
}
|
||||
|
||||
/** Called when the activity is closed. */
|
||||
override def onDestroy() {
|
||||
val settings: SharedPreferences = getSharedPreferences(Shadowsocks.PREFS_NAME, 0)
|
||||
val editor: SharedPreferences.Editor = settings.edit
|
||||
editor.putBoolean("isConnected", ShadowsocksService.isServiceStarted)
|
||||
editor.commit
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount == 0) {
|
||||
try {
|
||||
@@ -356,9 +362,7 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
protected override def onResume() {
|
||||
super.onResume()
|
||||
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val edit: SharedPreferences.Editor = settings.edit
|
||||
if (ShadowsocksService.isServiceStarted) {
|
||||
edit.putBoolean("isRunning", true)
|
||||
if (isServiceStarted) {
|
||||
switchButton.setChecked(true)
|
||||
}
|
||||
else {
|
||||
@@ -370,10 +374,8 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
edit.putBoolean("isRunning", false)
|
||||
switchButton.setChecked(false)
|
||||
}
|
||||
edit.commit
|
||||
setPreferenceEnabled()
|
||||
switchButton.setOnCheckedChangeListener(this)
|
||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
|
||||
@@ -406,10 +408,10 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
if (key == "isRunning") {
|
||||
if (settings.getBoolean("isRunning", false)) {
|
||||
switchButton.setChecked(true)
|
||||
if (!switchButton.isChecked) switchButton.setChecked(true)
|
||||
}
|
||||
else {
|
||||
switchButton.setChecked(false)
|
||||
if (switchButton.isChecked) switchButton.setChecked(false)
|
||||
}
|
||||
}
|
||||
if (key == "isConnecting") {
|
||||
@@ -450,7 +452,12 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
crash_recovery()
|
||||
copyAssets("")
|
||||
copyAssets(Utils.getABI)
|
||||
Utils.runCommand("chmod 755 /data/data/com.github.shadowsocks/iptables\n" + "chmod 755 /data/data/com.github.shadowsocks/redsocks\n" + "chmod 755 /data/data/com.github.shadowsocks/pdnsd\n" + "chmod 755 /data/data/com.github.shadowsocks/shadowsocks\n" + "chmod 755 /data/data/com.github.shadowsocks/polipo\n")
|
||||
Utils.runCommand("chmod 755 /data/data/com.github.shadowsocks/iptables\n"
|
||||
+ "chmod 755 /data/data/com.github.shadowsocks/redsocks\n"
|
||||
+ "chmod 755 /data/data/com.github.shadowsocks/pdnsd\n"
|
||||
+ "chmod 755 /data/data/com.github.shadowsocks/shadowsocks\n"
|
||||
+ "chmod 755 /data/data/com.github.shadowsocks/polipo\n"
|
||||
+ "chmod 755 /data/data/com.github.shadowsocks/tun2socks\n")
|
||||
}
|
||||
|
||||
private def recovery() {
|
||||
@@ -465,7 +472,7 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
}
|
||||
}
|
||||
}
|
||||
stopService(new Intent(this, classOf[ShadowsocksService]))
|
||||
serviceStop()
|
||||
new Thread {
|
||||
override def run() {
|
||||
reset()
|
||||
@@ -474,12 +481,43 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
}.start()
|
||||
}
|
||||
|
||||
/** Called when connect button is clicked. */
|
||||
def serviceStart: Boolean = {
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
|
||||
resultCode match {
|
||||
case Activity.RESULT_OK => {
|
||||
if (!serviceStart) {
|
||||
switchButton.setChecked(false)
|
||||
}
|
||||
}
|
||||
case _ => {
|
||||
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isVpnEnabled: Boolean = {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !Utils.getRoot
|
||||
}
|
||||
|
||||
def isServiceStarted: Boolean = {
|
||||
ShadowsocksService.isServiceStarted || ShadowVpnService.isServiceStarted
|
||||
}
|
||||
|
||||
def serviceStop() {
|
||||
if (ShadowVpnService.isServiceStarted) {
|
||||
if (ShadowVpnService.sConn != null) {
|
||||
ShadowVpnService.sConn.close()
|
||||
ShadowVpnService.sConn = null
|
||||
}
|
||||
stopService(new Intent(this, classOf[ShadowVpnService]))
|
||||
}
|
||||
if (ShadowsocksService.isServiceStarted) {
|
||||
stopService(new Intent(this, classOf[ShadowsocksService]))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when connect button is clicked. */
|
||||
def serviceStart: Boolean = {
|
||||
|
||||
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val proxy = settings.getString("proxy", "")
|
||||
if (isTextEmpty(proxy, getString(R.string.proxy_empty))) return false
|
||||
@@ -498,8 +536,17 @@ class Shadowsocks extends UnifiedSherlockPreferenceActivity with CompoundButton.
|
||||
return false
|
||||
}
|
||||
}
|
||||
val it: Intent = new Intent(this, classOf[ShadowsocksService])
|
||||
startService(it)
|
||||
|
||||
if (isVpnEnabled) {
|
||||
if (ShadowsocksService.isServiceStarted) return false
|
||||
val it: Intent = new Intent(this, classOf[ShadowsocksService])
|
||||
startService(it)
|
||||
} else {
|
||||
if (ShadowVpnService.isServiceStarted) return false
|
||||
val it: Intent = new Intent(this, classOf[ShadowVpnService])
|
||||
startService(it)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,16 @@ class ShadowsocksReceiver extends BroadcastReceiver {
|
||||
return
|
||||
}
|
||||
}
|
||||
val it: Intent = new Intent(context, classOf[ShadowsocksService])
|
||||
context.startService(it)
|
||||
|
||||
if (Utils.getRoot) {
|
||||
if (ShadowsocksService.isServiceStarted) return
|
||||
val it: Intent = new Intent(context, classOf[ShadowsocksService])
|
||||
context.startService(it)
|
||||
} else {
|
||||
if (ShadowVpnService.isServiceStarted) return
|
||||
val it: Intent = new Intent(context, classOf[ShadowVpnService])
|
||||
context.startService(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@ import android.os.Environment
|
||||
import android.util.Log
|
||||
import java.io._
|
||||
import java.net.NetworkInterface
|
||||
import java.util.ArrayList
|
||||
import org.apache.http.conn.util.InetAddressUtils
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
@@ -263,6 +262,9 @@ object Utils {
|
||||
val lines = sb.toString()
|
||||
if (lines.contains("system")) {
|
||||
isRoot = 1
|
||||
} else {
|
||||
if (rootTries > 3) isRoot = 0
|
||||
rootTries += 1
|
||||
}
|
||||
isRoot == 1
|
||||
}
|
||||
@@ -302,7 +304,7 @@ object Utils {
|
||||
runner.join()
|
||||
}
|
||||
if (runner.isAlive) {
|
||||
runner.destroy
|
||||
runner.destroy()
|
||||
runner.join(1000)
|
||||
return TIME_OUT
|
||||
}
|
||||
@@ -335,6 +337,7 @@ object Utils {
|
||||
var root_shell: String = null
|
||||
var iptables: String = null
|
||||
var data_path: String = null
|
||||
var rootTries = 0
|
||||
|
||||
/**
|
||||
* Internal thread used to execute scripts (as root or not).
|
||||
@@ -345,8 +348,8 @@ object Utils {
|
||||
* @param asroot if true, executes the script as root
|
||||
*/
|
||||
class ScriptRunner(val scripts: String,
|
||||
val result: StringBuilder,
|
||||
val asroot: Boolean) extends Thread {
|
||||
val result: StringBuilder,
|
||||
val asroot: Boolean) extends Thread {
|
||||
override def destroy() {
|
||||
if (pid(0) != -1) {
|
||||
Exec.hangupProcessGroup(pid(0))
|
||||
@@ -421,7 +424,7 @@ object Utils {
|
||||
}
|
||||
if (result == null || pipe == null) return
|
||||
val stdout: InputStream = new FileInputStream(pipe)
|
||||
val buf= new Array[Byte](8192)
|
||||
val buf = new Array[Byte](8192)
|
||||
var read: Int = 0
|
||||
while (stdout.available > 0) {
|
||||
read = stdout.read(buf)
|
||||
|
||||
Reference in New Issue
Block a user