Merge pull request #597 from Mygod/master
Refine #574, #583, #585, #594
|
Before Width: | Height: | Size: 221 B |
|
Before Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 430 B |
|
Before Width: | Height: | Size: 387 B |
|
Before Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 175 B |
|
Before Width: | Height: | Size: 88 B |
|
Before Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 343 B |
|
Before Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 359 B |
|
Before Width: | Height: | Size: 257 B |
|
Before Width: | Height: | Size: 97 B |
|
Before Width: | Height: | Size: 483 B |
|
Before Width: | Height: | Size: 571 B |
|
Before Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 605 B |
|
Before Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 97 B |
|
Before Width: | Height: | Size: 675 B |
|
Before Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 789 B |
|
Before Width: | Height: | Size: 835 B |
|
Before Width: | Height: | Size: 436 B |
|
Before Width: | Height: | Size: 102 B |
|
Before Width: | Height: | Size: 888 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -39,67 +39,59 @@
|
||||
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.util.Log
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.io.{IOException, InputStream, OutputStream}
|
||||
import java.lang.System.currentTimeMillis
|
||||
import java.util.concurrent.Semaphore
|
||||
|
||||
import collection.JavaConversions._
|
||||
import android.util.Log
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
/**
|
||||
* @author ayanamist@gmail.com
|
||||
*/
|
||||
class GuardedProcess (cmd: Seq[String], onRestartCallback: Runnable = null) extends Process {
|
||||
|
||||
class GuardedProcess(cmd: Seq[String]) extends Process {
|
||||
private val TAG = classOf[GuardedProcess].getSimpleName
|
||||
|
||||
@volatile private var guardThread: Thread = null
|
||||
@volatile private var isDestroyed: Boolean = false
|
||||
@volatile private var process: Process = null
|
||||
@volatile private var guardThread: Thread = _
|
||||
@volatile private var isDestroyed: Boolean = _
|
||||
@volatile private var process: Process = _
|
||||
|
||||
def start(): GuardedProcess = {
|
||||
def start(onRestartCallback: () => Unit = null): GuardedProcess = {
|
||||
val semaphore = new Semaphore(1)
|
||||
semaphore.acquire
|
||||
@volatile var ioException: IOException = null
|
||||
|
||||
val atomicIoException = new AtomicReference[IOException](null)
|
||||
val countDownLatch = new CountDownLatch(1)
|
||||
guardThread = new Thread(() => {
|
||||
try {
|
||||
var callback: () => Unit = null
|
||||
while (!isDestroyed) {
|
||||
Log.i(TAG, "start process: " + cmd)
|
||||
val startTime = currentTimeMillis
|
||||
|
||||
guardThread = new Thread(new Runnable() {
|
||||
override def run() {
|
||||
try {
|
||||
while (!isDestroyed) {
|
||||
Log.i(TAG, "start process: " + cmd)
|
||||
val startTime = java.lang.System.currentTimeMillis
|
||||
process = new ProcessBuilder(cmd).redirectErrorStream(true).start
|
||||
|
||||
process = new ProcessBuilder(cmd).redirectErrorStream(true).start
|
||||
if (onRestartCallback != null && countDownLatch.getCount <= 0) {
|
||||
onRestartCallback.run()
|
||||
}
|
||||
if (callback == null) callback = onRestartCallback else callback()
|
||||
|
||||
countDownLatch.countDown()
|
||||
process.waitFor
|
||||
semaphore.release
|
||||
process.waitFor
|
||||
|
||||
if (java.lang.System.currentTimeMillis - startTime < 1000) {
|
||||
Log.w(TAG, "process exit too fast, stop guard: " + cmd)
|
||||
return
|
||||
}
|
||||
if (currentTimeMillis - startTime < 1000) {
|
||||
Log.w(TAG, "process exit too fast, stop guard: " + cmd)
|
||||
isDestroyed = true
|
||||
}
|
||||
} catch {
|
||||
case ignored: InterruptedException =>
|
||||
Log.i(TAG, "thread interrupt, destroy process: " + cmd)
|
||||
process.destroy()
|
||||
case e: IOException =>
|
||||
atomicIoException.compareAndSet(null, e)
|
||||
} finally {
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ignored: InterruptedException =>
|
||||
Log.i(TAG, "thread interrupt, destroy process: " + cmd)
|
||||
process.destroy()
|
||||
case e: IOException => ioException = e
|
||||
} finally semaphore.release
|
||||
}, "GuardThread-" + cmd)
|
||||
|
||||
guardThread.start()
|
||||
countDownLatch.await()
|
||||
semaphore.acquire
|
||||
|
||||
val ioException: IOException = atomicIoException.get
|
||||
if (ioException != null) {
|
||||
throw ioException
|
||||
}
|
||||
@@ -111,32 +103,18 @@ class GuardedProcess (cmd: Seq[String], onRestartCallback: Runnable = null) exte
|
||||
isDestroyed = true
|
||||
guardThread.interrupt()
|
||||
process.destroy()
|
||||
try {
|
||||
guardThread.join()
|
||||
}
|
||||
catch {
|
||||
try guardThread.join() catch {
|
||||
case ignored: InterruptedException =>
|
||||
}
|
||||
}
|
||||
|
||||
def exitValue: Int = {
|
||||
throw new UnsupportedOperationException
|
||||
}
|
||||
|
||||
def getErrorStream: InputStream = {
|
||||
throw new UnsupportedOperationException
|
||||
}
|
||||
|
||||
def getInputStream: InputStream = {
|
||||
throw new UnsupportedOperationException
|
||||
}
|
||||
|
||||
def getOutputStream: OutputStream = {
|
||||
throw new UnsupportedOperationException
|
||||
}
|
||||
def exitValue: Int = throw new UnsupportedOperationException
|
||||
def getErrorStream: InputStream = throw new UnsupportedOperationException
|
||||
def getInputStream: InputStream = throw new UnsupportedOperationException
|
||||
def getOutputStream: OutputStream = throw new UnsupportedOperationException
|
||||
|
||||
@throws(classOf[InterruptedException])
|
||||
def waitFor: Int = {
|
||||
def waitFor = {
|
||||
guardThread.join()
|
||||
0
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import com.github.shadowsocks.utils.Action
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
trait ServiceBoundContext extends Context { outer =>
|
||||
trait ServiceBoundContext extends Context {
|
||||
class ShadowsocksServiceConnection extends ServiceConnection {
|
||||
override def onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
service.linkToDeath(new ShadowsocksDeathRecipient(ServiceBoundContext.this), 0)
|
||||
bgService = IShadowsocksService.Stub.asInterface(service)
|
||||
bgService.asBinder().linkToDeath(new ShadowsocksDeathRecipient(outer), 0)
|
||||
registerCallback
|
||||
ServiceBoundContext.this.onServiceConnected()
|
||||
}
|
||||
@@ -26,18 +26,18 @@ trait ServiceBoundContext extends Context { outer =>
|
||||
}
|
||||
}
|
||||
|
||||
def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try {
|
||||
protected def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try {
|
||||
bgService.registerCallback(callback)
|
||||
callbackRegistered = true
|
||||
} catch {
|
||||
case ignored: RemoteException => // Nothing
|
||||
}
|
||||
|
||||
def unregisterCallback = if (bgService != null && callback != null && callbackRegistered) try {
|
||||
bgService.unregisterCallback(callback)
|
||||
protected def unregisterCallback = {
|
||||
if (bgService != null && callback != null && callbackRegistered) try bgService.unregisterCallback(callback) catch {
|
||||
case ignored: RemoteException =>
|
||||
}
|
||||
callbackRegistered = false
|
||||
} catch {
|
||||
case ignored: RemoteException => callbackRegistered = false
|
||||
}
|
||||
|
||||
def onServiceConnected() = ()
|
||||
@@ -66,16 +66,12 @@ trait ServiceBoundContext extends Context { outer =>
|
||||
}
|
||||
|
||||
def deattachService() {
|
||||
if (bgService != null) {
|
||||
if (callback != null) {
|
||||
unregisterCallback
|
||||
callback = null
|
||||
}
|
||||
if (connection != null) {
|
||||
unbindService(connection)
|
||||
connection = null
|
||||
}
|
||||
bgService = null
|
||||
unregisterCallback
|
||||
callback = null
|
||||
if (connection != null) {
|
||||
unbindService(connection)
|
||||
connection = null
|
||||
}
|
||||
bgService = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
private def crashRecovery() {
|
||||
def crashRecovery() {
|
||||
val cmd = new ArrayBuffer[String]()
|
||||
|
||||
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||
@@ -318,8 +318,6 @@ class Shadowsocks
|
||||
changeSwitch(checked = false)
|
||||
}
|
||||
|
||||
def isReady: Boolean = checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null
|
||||
|
||||
def prepareStartService() {
|
||||
ThrowableFuture {
|
||||
if (ShadowsocksApplication.isVpnEnabled) {
|
||||
@@ -358,18 +356,9 @@ class Shadowsocks
|
||||
|
||||
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
|
||||
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
|
||||
fab.setOnClickListener((v: View) => {
|
||||
serviceStarted = !serviceStarted
|
||||
serviceStarted match {
|
||||
case true =>
|
||||
if (isReady)
|
||||
prepareStartService()
|
||||
else
|
||||
changeSwitch(checked = false)
|
||||
case false =>
|
||||
serviceStop()
|
||||
}
|
||||
})
|
||||
fab.setOnClickListener((v: View) => if (serviceStarted) serviceStop()
|
||||
else if (checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null) prepareStartService()
|
||||
else changeSwitch(checked = false))
|
||||
fab.setOnLongClickListener((v: View) => {
|
||||
Utils.positionToast(Toast.makeText(this, if (serviceStarted) R.string.stop else R.string.connect,
|
||||
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show
|
||||
@@ -377,11 +366,6 @@ class Shadowsocks
|
||||
})
|
||||
updateTraffic(0, 0, 0, 0)
|
||||
|
||||
bindToService()
|
||||
}
|
||||
|
||||
// Bind to the service
|
||||
def bindToService() {
|
||||
handler.post(() => attachService)
|
||||
}
|
||||
|
||||
@@ -488,7 +472,9 @@ class Shadowsocks
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
def install() {
|
||||
def reset() {
|
||||
crashRecovery()
|
||||
|
||||
copyAssets(System.getABI)
|
||||
|
||||
val ab = new ArrayBuffer[String]
|
||||
@@ -498,12 +484,6 @@ class Shadowsocks
|
||||
Console.runCommand(ab.toArray)
|
||||
}
|
||||
|
||||
def reset() {
|
||||
crashRecovery()
|
||||
|
||||
install()
|
||||
}
|
||||
|
||||
def recovery() {
|
||||
serviceStop()
|
||||
val h = showProgress(R.string.recovering)
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.github.shadowsocks
|
||||
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.utils.ProcessUtils._
|
||||
|
||||
/**
|
||||
* @author chentaov5@gmail.com
|
||||
@@ -14,15 +13,12 @@ class ShadowsocksDeathRecipient(val mContext: ServiceBoundContext)
|
||||
|
||||
override def binderDied(): Unit = {
|
||||
Log.d(TAG, "[ShadowsocksDeathRecipient] binder died.")
|
||||
|
||||
mContext match {
|
||||
case ss: Shadowsocks =>
|
||||
inShadowsocks(mContext) {
|
||||
ss.unregisterCallback
|
||||
ss.bindToService()
|
||||
}
|
||||
ss.deattachService
|
||||
ss.crashRecovery
|
||||
ss.attachService
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import android.webkit.{WebView, WebViewClient}
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.preferences._
|
||||
import com.github.shadowsocks.utils.CloseUtils._
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
import com.github.shadowsocks.utils.Key
|
||||
|
||||
object ShadowsocksSettings {
|
||||
// Constants
|
||||
@@ -196,16 +196,10 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
private var enabled = true
|
||||
def setEnabled(enabled: Boolean) {
|
||||
this.enabled = enabled
|
||||
findPreference(Key.isNAT).setEnabled(enabled)
|
||||
for (name <- PROXY_PREFS) {
|
||||
for (name <- Key.isNAT #:: PROXY_PREFS.toStream #::: FEATURE_PREFS.toStream) {
|
||||
val pref = findPreference(name)
|
||||
if (pref != null) pref.setEnabled(enabled)
|
||||
}
|
||||
for (name <- FEATURE_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
if (pref != null) pref.setEnabled(enabled &&
|
||||
(name != Key.isProxyApps || Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
|
||||
}
|
||||
}
|
||||
|
||||
def update(profile: Profile) {
|
||||
|
||||
@@ -44,18 +44,16 @@ import java.lang.Process
|
||||
import java.util.Locale
|
||||
|
||||
import android.content._
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.VpnService
|
||||
import android.os._
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
import com.github.shadowsocks.utils._
|
||||
import org.apache.commons.net.util.SubnetUtils
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
@@ -434,11 +432,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
|
||||
tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq, new Runnable {
|
||||
override def run(): Unit = {
|
||||
sendFd(fd)
|
||||
}
|
||||
}).start()
|
||||
tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq).start(() => sendFd(fd))
|
||||
|
||||
fd
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ package com.github
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Success, Failure, Try}
|
||||
import scala.util.{Failure, Try}
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
package object shadowsocks {
|
||||
|
||||
val handleFailure: PartialFunction[Try[_], Unit] = {
|
||||
case Success(_) =>
|
||||
private val handleFailure: Try[_] => Unit = {
|
||||
case Failure(e) => e.printStackTrace()
|
||||
case _ =>
|
||||
}
|
||||
|
||||
def ThrowableFuture[T](f: => T) = Future(f) onComplete handleFailure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ package com.github.shadowsocks.utils
|
||||
* @author Mygod
|
||||
*/
|
||||
object CloseUtils {
|
||||
type Closeable = {
|
||||
def close()
|
||||
}
|
||||
type Disconnectable = {
|
||||
def disconnect()
|
||||
}
|
||||
|
||||
def autoClose[A <: AutoCloseable, B](x: => A)(block: A => B): B = {
|
||||
def autoClose[A <: Closeable, B](x: => A)(block: A => B): B = {
|
||||
var a: Option[A] = None
|
||||
try {
|
||||
a = Some(x)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* @author chentaov5@gmail.com
|
||||
*/
|
||||
object IOUtils {
|
||||
|
||||
val TAG = "IOUtils"
|
||||
|
||||
def autoClose[R <: {def close() : Unit}, T](in: R)(fun: => T): T = {
|
||||
try {
|
||||
fun
|
||||
} finally if (in ne null)
|
||||
try {
|
||||
in.close()
|
||||
} catch {
|
||||
case e: Exception => Log.d(TAG, e.getMessage, e)
|
||||
}
|
||||
}
|
||||
|
||||
def inSafe[T](fun: => T): Option[T] = {
|
||||
var res: Option[T] = None
|
||||
try {
|
||||
res = Some(fun)
|
||||
} catch {
|
||||
case e: Exception => Log.d(TAG, e.getMessage, e)
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.io.{BufferedReader, FileInputStream, InputStreamReader}
|
||||
|
||||
import android.content.Context
|
||||
import com.github.shadowsocks.utils.IOUtils._
|
||||
|
||||
/**
|
||||
* @author chentaov5@gmail.com
|
||||
*/
|
||||
object ProcessUtils {
|
||||
|
||||
def getCurrentProcessName: String = {
|
||||
inSafe {
|
||||
val inputStream = new FileInputStream("/proc/self/cmdline")
|
||||
val reader = new BufferedReader(new InputStreamReader(inputStream))
|
||||
autoClose(reader) {
|
||||
val sb = new StringBuilder
|
||||
var c: Int = 0
|
||||
while({c = reader.read(); c > 0})
|
||||
sb.append(c.asInstanceOf[Char])
|
||||
sb.toString()
|
||||
}
|
||||
} match {
|
||||
case Some(name: String) => name
|
||||
case None => ""
|
||||
}
|
||||
}
|
||||
|
||||
def inShadowsocks[A](context: Context)(fun: => A): Unit = {
|
||||
if (context != null && getCurrentProcessName.equals(context.getPackageName)) fun
|
||||
}
|
||||
|
||||
|
||||
def inVpn[A](context: Context)(fun: => A): Unit = {
|
||||
if (context != null && getCurrentProcessName.equals(context.getPackageName + ":vpn")) fun
|
||||
}
|
||||
|
||||
|
||||
def inNat[A](context: Context)(fun: => A): Unit = {
|
||||
if (context != null && getCurrentProcessName.equals(context.getPackageName + ":nat")) fun
|
||||
}
|
||||
|
||||
}
|
||||