Commit 3ae15514 authored by Max Lv's avatar Max Lv

Merge pull request #597 from Mygod/master

Refine #574, #583, #585, #594
parents cee74f25 4c1a817c
...@@ -39,67 +39,59 @@ ...@@ -39,67 +39,59 @@
package com.github.shadowsocks package com.github.shadowsocks
import java.io.{IOException, InputStream, OutputStream}
import java.lang.System.currentTimeMillis
import java.util.concurrent.Semaphore
import android.util.Log 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 collection.JavaConversions._ import scala.collection.JavaConversions._
/** /**
* @author ayanamist@gmail.com * @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 private val TAG = classOf[GuardedProcess].getSimpleName
@volatile private var guardThread: Thread = null @volatile private var guardThread: Thread = _
@volatile private var isDestroyed: Boolean = false @volatile private var isDestroyed: Boolean = _
@volatile private var process: Process = null @volatile private var process: Process = _
def start(): GuardedProcess = {
val atomicIoException = new AtomicReference[IOException](null) def start(onRestartCallback: () => Unit = null): GuardedProcess = {
val countDownLatch = new CountDownLatch(1) val semaphore = new Semaphore(1)
semaphore.acquire
@volatile var ioException: IOException = null
guardThread = new Thread(new Runnable() { guardThread = new Thread(() => {
override def run() {
try { try {
var callback: () => Unit = null
while (!isDestroyed) { while (!isDestroyed) {
Log.i(TAG, "start process: " + cmd) Log.i(TAG, "start process: " + cmd)
val startTime = java.lang.System.currentTimeMillis val startTime = currentTimeMillis
process = new ProcessBuilder(cmd).redirectErrorStream(true).start process = new ProcessBuilder(cmd).redirectErrorStream(true).start
if (onRestartCallback != null && countDownLatch.getCount <= 0) {
onRestartCallback.run()
}
countDownLatch.countDown() if (callback == null) callback = onRestartCallback else callback()
semaphore.release
process.waitFor process.waitFor
if (java.lang.System.currentTimeMillis - startTime < 1000) { if (currentTimeMillis - startTime < 1000) {
Log.w(TAG, "process exit too fast, stop guard: " + cmd) Log.w(TAG, "process exit too fast, stop guard: " + cmd)
return isDestroyed = true
} }
} }
} catch { } catch {
case ignored: InterruptedException => case ignored: InterruptedException =>
Log.i(TAG, "thread interrupt, destroy process: " + cmd) Log.i(TAG, "thread interrupt, destroy process: " + cmd)
process.destroy() process.destroy()
case e: IOException => case e: IOException => ioException = e
atomicIoException.compareAndSet(null, e) } finally semaphore.release
} finally {
countDownLatch.countDown()
}
}
}, "GuardThread-" + cmd) }, "GuardThread-" + cmd)
guardThread.start() guardThread.start()
countDownLatch.await() semaphore.acquire
val ioException: IOException = atomicIoException.get
if (ioException != null) { if (ioException != null) {
throw ioException throw ioException
} }
...@@ -111,32 +103,18 @@ class GuardedProcess (cmd: Seq[String], onRestartCallback: Runnable = null) exte ...@@ -111,32 +103,18 @@ class GuardedProcess (cmd: Seq[String], onRestartCallback: Runnable = null) exte
isDestroyed = true isDestroyed = true
guardThread.interrupt() guardThread.interrupt()
process.destroy() process.destroy()
try { try guardThread.join() catch {
guardThread.join()
}
catch {
case ignored: InterruptedException => case ignored: InterruptedException =>
} }
} }
def exitValue: Int = { def exitValue: Int = throw new UnsupportedOperationException
throw new UnsupportedOperationException def getErrorStream: InputStream = throw new UnsupportedOperationException
} def getInputStream: InputStream = throw new UnsupportedOperationException
def getOutputStream: OutputStream = throw new UnsupportedOperationException
def getErrorStream: InputStream = {
throw new UnsupportedOperationException
}
def getInputStream: InputStream = {
throw new UnsupportedOperationException
}
def getOutputStream: OutputStream = {
throw new UnsupportedOperationException
}
@throws(classOf[InterruptedException]) @throws(classOf[InterruptedException])
def waitFor: Int = { def waitFor = {
guardThread.join() guardThread.join()
0 0
} }
......
...@@ -8,11 +8,11 @@ import com.github.shadowsocks.utils.Action ...@@ -8,11 +8,11 @@ import com.github.shadowsocks.utils.Action
/** /**
* @author Mygod * @author Mygod
*/ */
trait ServiceBoundContext extends Context { outer => trait ServiceBoundContext extends Context {
class ShadowsocksServiceConnection extends ServiceConnection { class ShadowsocksServiceConnection extends ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) { override def onServiceConnected(name: ComponentName, service: IBinder) {
service.linkToDeath(new ShadowsocksDeathRecipient(ServiceBoundContext.this), 0)
bgService = IShadowsocksService.Stub.asInterface(service) bgService = IShadowsocksService.Stub.asInterface(service)
bgService.asBinder().linkToDeath(new ShadowsocksDeathRecipient(outer), 0)
registerCallback registerCallback
ServiceBoundContext.this.onServiceConnected() ServiceBoundContext.this.onServiceConnected()
} }
...@@ -26,18 +26,18 @@ trait ServiceBoundContext extends Context { outer => ...@@ -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) bgService.registerCallback(callback)
callbackRegistered = true callbackRegistered = true
} catch { } catch {
case ignored: RemoteException => // Nothing case ignored: RemoteException => // Nothing
} }
def unregisterCallback = if (bgService != null && callback != null && callbackRegistered) try { protected def unregisterCallback = {
bgService.unregisterCallback(callback) if (bgService != null && callback != null && callbackRegistered) try bgService.unregisterCallback(callback) catch {
case ignored: RemoteException =>
}
callbackRegistered = false callbackRegistered = false
} catch {
case ignored: RemoteException => callbackRegistered = false
} }
def onServiceConnected() = () def onServiceConnected() = ()
...@@ -66,16 +66,12 @@ trait ServiceBoundContext extends Context { outer => ...@@ -66,16 +66,12 @@ trait ServiceBoundContext extends Context { outer =>
} }
def deattachService() { def deattachService() {
if (bgService != null) {
if (callback != null) {
unregisterCallback unregisterCallback
callback = null callback = null
}
if (connection != null) { if (connection != null) {
unbindService(connection) unbindService(connection)
connection = null connection = null
} }
bgService = null bgService = null
} }
}
} }
...@@ -275,7 +275,7 @@ class Shadowsocks ...@@ -275,7 +275,7 @@ class Shadowsocks
} }
} }
private def crashRecovery() { def crashRecovery() {
val cmd = new ArrayBuffer[String]() val cmd = new ArrayBuffer[String]()
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) { for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
...@@ -318,8 +318,6 @@ class Shadowsocks ...@@ -318,8 +318,6 @@ class Shadowsocks
changeSwitch(checked = false) changeSwitch(checked = false)
} }
def isReady: Boolean = checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null
def prepareStartService() { def prepareStartService() {
ThrowableFuture { ThrowableFuture {
if (ShadowsocksApplication.isVpnEnabled) { if (ShadowsocksApplication.isVpnEnabled) {
...@@ -358,18 +356,9 @@ class Shadowsocks ...@@ -358,18 +356,9 @@ class Shadowsocks
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
fab.setOnClickListener((v: View) => { fab.setOnClickListener((v: View) => if (serviceStarted) serviceStop()
serviceStarted = !serviceStarted else if (checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null) prepareStartService()
serviceStarted match { else changeSwitch(checked = false))
case true =>
if (isReady)
prepareStartService()
else
changeSwitch(checked = false)
case false =>
serviceStop()
}
})
fab.setOnLongClickListener((v: View) => { fab.setOnLongClickListener((v: View) => {
Utils.positionToast(Toast.makeText(this, if (serviceStarted) R.string.stop else R.string.connect, 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 Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show
...@@ -377,11 +366,6 @@ class Shadowsocks ...@@ -377,11 +366,6 @@ class Shadowsocks
}) })
updateTraffic(0, 0, 0, 0) updateTraffic(0, 0, 0, 0)
bindToService()
}
// Bind to the service
def bindToService() {
handler.post(() => attachService) handler.post(() => attachService)
} }
...@@ -488,7 +472,9 @@ class Shadowsocks ...@@ -488,7 +472,9 @@ class Shadowsocks
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
} }
def install() { def reset() {
crashRecovery()
copyAssets(System.getABI) copyAssets(System.getABI)
val ab = new ArrayBuffer[String] val ab = new ArrayBuffer[String]
...@@ -498,12 +484,6 @@ class Shadowsocks ...@@ -498,12 +484,6 @@ class Shadowsocks
Console.runCommand(ab.toArray) Console.runCommand(ab.toArray)
} }
def reset() {
crashRecovery()
install()
}
def recovery() { def recovery() {
serviceStop() serviceStop()
val h = showProgress(R.string.recovering) val h = showProgress(R.string.recovering)
......
...@@ -2,7 +2,6 @@ package com.github.shadowsocks ...@@ -2,7 +2,6 @@ package com.github.shadowsocks
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import com.github.shadowsocks.utils.ProcessUtils._
/** /**
* @author chentaov5@gmail.com * @author chentaov5@gmail.com
...@@ -14,15 +13,12 @@ class ShadowsocksDeathRecipient(val mContext: ServiceBoundContext) ...@@ -14,15 +13,12 @@ class ShadowsocksDeathRecipient(val mContext: ServiceBoundContext)
override def binderDied(): Unit = { override def binderDied(): Unit = {
Log.d(TAG, "[ShadowsocksDeathRecipient] binder died.") Log.d(TAG, "[ShadowsocksDeathRecipient] binder died.")
mContext match { mContext match {
case ss: Shadowsocks => case ss: Shadowsocks =>
inShadowsocks(mContext) { ss.deattachService
ss.unregisterCallback ss.crashRecovery
ss.bindToService() ss.attachService
}
case _ => case _ =>
} }
} }
} }
...@@ -15,7 +15,7 @@ import android.webkit.{WebView, WebViewClient} ...@@ -15,7 +15,7 @@ import android.webkit.{WebView, WebViewClient}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.preferences._ import com.github.shadowsocks.preferences._
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.{Key, Utils} import com.github.shadowsocks.utils.Key
object ShadowsocksSettings { object ShadowsocksSettings {
// Constants // Constants
...@@ -196,16 +196,10 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan ...@@ -196,16 +196,10 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
private var enabled = true private var enabled = true
def setEnabled(enabled: Boolean) { def setEnabled(enabled: Boolean) {
this.enabled = enabled this.enabled = enabled
findPreference(Key.isNAT).setEnabled(enabled) for (name <- Key.isNAT #:: PROXY_PREFS.toStream #::: FEATURE_PREFS.toStream) {
for (name <- PROXY_PREFS) {
val pref = findPreference(name) val pref = findPreference(name)
if (pref != null) pref.setEnabled(enabled) 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) { def update(profile: Profile) {
......
...@@ -44,18 +44,16 @@ import java.lang.Process ...@@ -44,18 +44,16 @@ import java.lang.Process
import java.util.Locale import java.util.Locale
import android.content._ import android.content._
import android.content.pm.PackageManager.NameNotFoundException
import android.content.pm.{PackageInfo, PackageManager} import android.content.pm.{PackageInfo, PackageManager}
import android.net.VpnService import android.net.VpnService
import android.os._ import android.os._
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import android.content.pm.PackageManager.NameNotFoundException
import com.github.shadowsocks.aidl.Config import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import org.apache.commons.net.util.SubnetUtils import org.apache.commons.net.util.SubnetUtils
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
class ShadowsocksVpnService extends VpnService with BaseService { class ShadowsocksVpnService extends VpnService with BaseService {
...@@ -434,11 +432,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -434,11 +432,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (BuildConfig.DEBUG) Log.d(TAG, cmd) if (BuildConfig.DEBUG) Log.d(TAG, cmd)
tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq, new Runnable { tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq).start(() => sendFd(fd))
override def run(): Unit = {
sendFd(fd)
}
}).start()
fd fd
} }
......
...@@ -2,16 +2,15 @@ package com.github ...@@ -2,16 +2,15 @@ package com.github
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
import scala.util.{Success, Failure, Try} import scala.util.{Failure, Try}
/** /**
* @author Mygod * @author Mygod
*/ */
package object shadowsocks { package object shadowsocks {
private val handleFailure: Try[_] => Unit = {
val handleFailure: PartialFunction[Try[_], Unit] = {
case Success(_) =>
case Failure(e) => e.printStackTrace() case Failure(e) => e.printStackTrace()
case _ =>
} }
def ThrowableFuture[T](f: => T) = Future(f) onComplete handleFailure def ThrowableFuture[T](f: => T) = Future(f) onComplete handleFailure
......
...@@ -4,11 +4,14 @@ package com.github.shadowsocks.utils ...@@ -4,11 +4,14 @@ package com.github.shadowsocks.utils
* @author Mygod * @author Mygod
*/ */
object CloseUtils { object CloseUtils {
type Closeable = {
def close()
}
type Disconnectable = { type Disconnectable = {
def disconnect() 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 var a: Option[A] = None
try { try {
a = Some(x) a = Some(x)
......
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
}
}
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
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment