Merge pull request #602 from Mygod/restart
Automatic service restart (with zero latency) after switching profile
This commit is contained in:
@@ -10,6 +10,5 @@ interface IShadowsocksService {
|
||||
oneway void registerCallback(IShadowsocksServiceCallback cb);
|
||||
oneway void unregisterCallback(IShadowsocksServiceCallback cb);
|
||||
|
||||
oneway void start(in Config config);
|
||||
oneway void stop();
|
||||
oneway void use(in Config config);
|
||||
}
|
||||
|
||||
@@ -42,10 +42,12 @@ package com.github.shadowsocks
|
||||
import java.util.{Timer, TimerTask}
|
||||
|
||||
import android.app.Service
|
||||
import android.content.{Intent, Context}
|
||||
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
|
||||
import android.os.{Handler, RemoteCallbackList}
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
|
||||
import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread}
|
||||
import com.github.shadowsocks.utils._
|
||||
|
||||
trait BaseService extends Service {
|
||||
|
||||
@@ -57,6 +59,13 @@ trait BaseService extends Service {
|
||||
|
||||
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
|
||||
var callbacksCount: Int = _
|
||||
lazy val handler = new Handler(getContext.getMainLooper)
|
||||
|
||||
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => {
|
||||
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
|
||||
stopRunner()
|
||||
}
|
||||
var closeReceiverRegistered: Boolean = _
|
||||
|
||||
val binder = new IShadowsocksService.Stub {
|
||||
override def getMode: Int = {
|
||||
@@ -94,17 +103,15 @@ trait BaseService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
override def stop() {
|
||||
if (state == State.CONNECTED) {
|
||||
stopRunner()
|
||||
}
|
||||
}
|
||||
|
||||
override def start(config: Config) {
|
||||
if (state == State.STOPPED) {
|
||||
startRunner(config)
|
||||
}
|
||||
}
|
||||
override def use(config: Config) = synchronized(state match {
|
||||
case State.STOPPED => if (config != null) startRunner(config)
|
||||
case State.CONNECTED =>
|
||||
if (config == null) stopRunner() else if (config.profileId != BaseService.this.config.profileId) {
|
||||
stopRunner()
|
||||
startRunner(config)
|
||||
}
|
||||
case _ => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + state)
|
||||
})
|
||||
}
|
||||
|
||||
def startRunner(config: Config) {
|
||||
@@ -114,9 +121,24 @@ trait BaseService extends Service {
|
||||
TrafficMonitor.reset()
|
||||
trafficMonitorThread = new TrafficMonitorThread(getApplicationContext)
|
||||
trafficMonitorThread.start()
|
||||
|
||||
if (!closeReceiverRegistered) {
|
||||
// register close receiver
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Intent.ACTION_SHUTDOWN)
|
||||
filter.addAction(Action.CLOSE)
|
||||
registerReceiver(closeReceiver, filter)
|
||||
closeReceiverRegistered = true
|
||||
}
|
||||
}
|
||||
|
||||
def stopRunner() {
|
||||
// clean up recevier
|
||||
if (closeReceiverRegistered) {
|
||||
unregisterReceiver(closeReceiver)
|
||||
closeReceiverRegistered = false
|
||||
}
|
||||
|
||||
// Make sure update total traffic when stopping the runner
|
||||
updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
|
||||
|
||||
@@ -158,7 +180,6 @@ trait BaseService extends Service {
|
||||
}
|
||||
|
||||
def updateTrafficRate() {
|
||||
val handler = new Handler(getContext.getMainLooper)
|
||||
handler.post(() => {
|
||||
if (callbacksCount > 0) {
|
||||
val txRate = TrafficMonitor.txRate
|
||||
|
||||
@@ -328,7 +328,7 @@ class Shadowsocks
|
||||
handler.post(() => onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null))
|
||||
}
|
||||
} else {
|
||||
serviceStart()
|
||||
serviceLoad()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,21 +369,6 @@ class Shadowsocks
|
||||
handler.post(() => attachService)
|
||||
}
|
||||
|
||||
def reloadProfile() {
|
||||
currentProfile = ShadowsocksApplication.currentProfile match {
|
||||
case Some(profile) => profile // updated
|
||||
case None => // removed
|
||||
ShadowsocksApplication.profileManager.getFirstProfile match {
|
||||
case Some(first) => ShadowsocksApplication.switchProfile(first.id)
|
||||
case None => ShadowsocksApplication.profileManager.createDefault()
|
||||
}
|
||||
}
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
serviceStop()
|
||||
}
|
||||
|
||||
protected override def onPause() {
|
||||
super.onPause()
|
||||
ShadowsocksApplication.profileManager.save
|
||||
@@ -434,7 +419,20 @@ class Shadowsocks
|
||||
ConfigUtils.refresh(this)
|
||||
|
||||
// Check if current profile changed
|
||||
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
||||
if (ShadowsocksApplication.profileId != currentProfile.id) {
|
||||
currentProfile = ShadowsocksApplication.currentProfile match {
|
||||
case Some(profile) => profile // updated
|
||||
case None => // removed
|
||||
ShadowsocksApplication.profileManager.getFirstProfile match {
|
||||
case Some(first) => ShadowsocksApplication.switchProfile(first.id)
|
||||
case None => ShadowsocksApplication.profileManager.createDefault()
|
||||
}
|
||||
}
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
if (serviceStarted) serviceLoad()
|
||||
}
|
||||
|
||||
updateState()
|
||||
}
|
||||
@@ -485,7 +483,7 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
def recovery() {
|
||||
serviceStop()
|
||||
if (serviceStarted) serviceStop()
|
||||
val h = showProgress(R.string.recovering)
|
||||
ThrowableFuture {
|
||||
reset()
|
||||
@@ -504,14 +502,14 @@ class Shadowsocks
|
||||
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) = resultCode match {
|
||||
case Activity.RESULT_OK =>
|
||||
serviceStart()
|
||||
serviceLoad()
|
||||
case _ =>
|
||||
cancelStart()
|
||||
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
|
||||
}
|
||||
|
||||
def serviceStop() {
|
||||
if (bgService != null) bgService.stop()
|
||||
if (bgService != null) bgService.use(null)
|
||||
}
|
||||
|
||||
def checkText(key: String): Boolean = {
|
||||
@@ -522,8 +520,8 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
/** Called when connect button is clicked. */
|
||||
def serviceStart() {
|
||||
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
def serviceLoad() {
|
||||
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
|
||||
if (ShadowsocksApplication.isVpnEnabled) {
|
||||
changeSwitch(checked = false)
|
||||
|
||||
@@ -49,7 +49,6 @@ import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.{ConnectivityManager, Network}
|
||||
import android.os._
|
||||
import android.util.{Log, SparseArray}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
import com.github.shadowsocks.utils._
|
||||
|
||||
@@ -65,7 +64,6 @@ class ShadowsocksNatService extends BaseService {
|
||||
"-j DNAT --to-destination 127.0.0.1:8123"
|
||||
|
||||
private var notification: ShadowsocksNotification = _
|
||||
var closeReceiver: BroadcastReceiver = _
|
||||
var connReceiver: BroadcastReceiver = _
|
||||
val myUid = android.os.Process.myUid()
|
||||
|
||||
@@ -386,16 +384,6 @@ class ShadowsocksNatService extends BaseService {
|
||||
}
|
||||
super.startRunner(config)
|
||||
|
||||
// register close receiver
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Intent.ACTION_SHUTDOWN)
|
||||
filter.addAction(Action.CLOSE)
|
||||
closeReceiver = (context: Context, intent: Intent) => {
|
||||
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
|
||||
stopRunner()
|
||||
}
|
||||
registerReceiver(closeReceiver, filter)
|
||||
|
||||
ShadowsocksApplication.track(TAG, "start")
|
||||
|
||||
changeState(State.CONNECTING)
|
||||
@@ -450,12 +438,6 @@ class ShadowsocksNatService extends BaseService {
|
||||
// channge the state
|
||||
changeState(State.STOPPING)
|
||||
|
||||
// clean up recevier
|
||||
if (closeReceiver != null) {
|
||||
unregisterReceiver(closeReceiver)
|
||||
closeReceiver = null
|
||||
}
|
||||
|
||||
if (notification != null) notification.destroy()
|
||||
|
||||
ShadowsocksApplication.track(TAG, "stop")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.{Handler, Build, Bundle}
|
||||
import android.os.{Build, Bundle}
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.{Toolbar, DefaultItemAnimator, LinearLayoutManager, RecyclerView}
|
||||
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
|
||||
import android.view.{LayoutInflater, View, ViewGroup}
|
||||
import android.widget.CheckedTextView
|
||||
import com.github.shadowsocks.database.Profile
|
||||
@@ -31,12 +31,8 @@ class ShadowsocksQuickSwitchActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
def onClick(v: View) {
|
||||
Utils.stopSsService(ShadowsocksQuickSwitchActivity.this)
|
||||
ShadowsocksApplication.switchProfile(item.id)
|
||||
val handler = new Handler(ShadowsocksQuickSwitchActivity.this.getMainLooper)
|
||||
handler.postDelayed(() => {
|
||||
Utils.startSsService(ShadowsocksQuickSwitchActivity.this)
|
||||
}, 3000)
|
||||
Utils.startSsService(ShadowsocksQuickSwitchActivity.this)
|
||||
finish
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
|
||||
onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null)
|
||||
}
|
||||
} else {
|
||||
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
|
||||
resultCode match {
|
||||
case Activity.RESULT_OK =>
|
||||
if (bgService != null) {
|
||||
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
}
|
||||
case _ =>
|
||||
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
|
||||
|
||||
@@ -61,11 +61,11 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
|
||||
val intent = VpnService.prepare(ShadowsocksRunnerService.this)
|
||||
if (intent == null) {
|
||||
if (bgService != null) {
|
||||
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.VpnService
|
||||
import android.os._
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
import com.github.shadowsocks.utils._
|
||||
import org.apache.commons.net.util.SubnetUtils
|
||||
@@ -64,7 +63,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
var conn: ParcelFileDescriptor = _
|
||||
var vpnThread: ShadowsocksVpnThread = _
|
||||
private var notification: ShadowsocksNotification = _
|
||||
var closeReceiver: BroadcastReceiver = _
|
||||
|
||||
var sslocalProcess: Process = _
|
||||
var sstunnelProcess: Process = _
|
||||
@@ -134,12 +132,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
conn = null
|
||||
}
|
||||
|
||||
// clean up recevier
|
||||
if (closeReceiver != null) {
|
||||
unregisterReceiver(closeReceiver)
|
||||
closeReceiver = null
|
||||
}
|
||||
|
||||
super.stopRunner()
|
||||
}
|
||||
|
||||
@@ -181,16 +173,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
vpnThread = new ShadowsocksVpnThread(this)
|
||||
vpnThread.start()
|
||||
|
||||
// register close receiver
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Intent.ACTION_SHUTDOWN)
|
||||
filter.addAction(Action.CLOSE)
|
||||
closeReceiver = (context: Context, intent: Intent) => {
|
||||
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
|
||||
stopRunner()
|
||||
}
|
||||
registerReceiver(closeReceiver, filter)
|
||||
|
||||
// ensure the VPNService is prepared
|
||||
if (VpnService.prepare(this) != null) {
|
||||
val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
|
||||
|
||||
@@ -66,7 +66,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
case _: Exception => // ignore
|
||||
}
|
||||
serverSocket = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def stopThread() {
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.content.{BroadcastReceiver, Context, Intent}
|
||||
import android.os.Handler
|
||||
import com.github.shadowsocks.helper.TaskerSettings
|
||||
import com.github.shadowsocks.utils.Utils
|
||||
|
||||
@@ -51,15 +50,10 @@ class TaskerReceiver extends BroadcastReceiver {
|
||||
val settings = TaskerSettings.fromIntent(intent)
|
||||
val switched = ShadowsocksApplication.profileManager.getProfile(settings.profileId) match {
|
||||
case Some(p) =>
|
||||
Utils.stopSsService(context)
|
||||
ShadowsocksApplication.switchProfile(settings.profileId)
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
val handler = new Handler(context.getMainLooper)
|
||||
handler.postDelayed(() => {
|
||||
if (settings.switchOn) Utils.startSsService(context)
|
||||
else if (!switched) Utils.stopSsService(context)
|
||||
}, 3000)
|
||||
if (settings.switchOn) Utils.startSsService(context) else Utils.stopSsService(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ object Key {
|
||||
val isProxyApps = "isProxyApps"
|
||||
val isBypassApps = "isBypassApps"
|
||||
val isUdpDns = "isUdpDns"
|
||||
val isAuth= "isAuth"
|
||||
val isIpv6= "isIpv6"
|
||||
val isAuth = "isAuth"
|
||||
val isIpv6 = "isIpv6"
|
||||
|
||||
val proxy = "proxy"
|
||||
val sitekey = "sitekey"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.lang.System
|
||||
import java.text.DecimalFormat
|
||||
|
||||
import com.github.shadowsocks.{R, ShadowsocksApplication}
|
||||
@@ -81,4 +80,3 @@ object TrafficMonitor {
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import java.io._
|
||||
import java.net._
|
||||
import java.security.MessageDigest
|
||||
|
||||
import android.animation.{AnimatorListenerAdapter, Animator}
|
||||
import android.animation.{Animator, AnimatorListenerAdapter}
|
||||
import android.app.ActivityManager
|
||||
import android.content.pm.{ApplicationInfo, PackageManager}
|
||||
import android.content.{Context, Intent}
|
||||
@@ -51,11 +51,11 @@ import android.graphics.drawable.{BitmapDrawable, Drawable}
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.{DisplayMetrics, Base64, Log}
|
||||
import android.util.{Base64, DisplayMetrics, Log}
|
||||
import android.view.View.MeasureSpec
|
||||
import android.view.{Gravity, View, Window}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.{ShadowsocksRunnerService, ShadowsocksApplication, BuildConfig}
|
||||
import com.github.shadowsocks.{BuildConfig, ShadowsocksApplication, ShadowsocksRunnerService}
|
||||
import org.xbill.DNS._
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
def startSsService(context: Context): Unit = {
|
||||
def startSsService(context: Context) {
|
||||
val isInstalled: Boolean = ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)
|
||||
if (!isInstalled) return
|
||||
|
||||
@@ -409,8 +409,9 @@ object Utils {
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
def stopSsService(context: Context): Unit = {
|
||||
context.sendBroadcast(new Intent(Action.CLOSE))
|
||||
def stopSsService(context: Context) {
|
||||
val intent = new Intent(Action.CLOSE)
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user