diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 8086c1e9..f4d102ed 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -96,18 +96,14 @@
android:label="@string/quick_toggle"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
- android:taskAffinity=""
+ android:taskAffinity=""
+ android:process=":bg"
android:launchMode="singleTask">
-
-
-
-
+
-
+
diff --git a/mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl b/mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl
index 17545289..5735de28 100644
--- a/mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl
+++ b/mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl
@@ -10,7 +10,4 @@ interface IShadowsocksService {
oneway void startListeningForBandwidth(IShadowsocksServiceCallback cb);
oneway void stopListeningForBandwidth(IShadowsocksServiceCallback cb);
oneway void unregisterCallback(IShadowsocksServiceCallback cb);
-
- oneway void use(in int profileId);
- void useSync(in int profileId);
}
diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
index 3c9835e1..83765492 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala
@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit
import java.util.{Timer, TimerTask}
import android.app.Service
-import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
+import android.content._
import android.os.{Build, Handler, IBinder, RemoteCallbackList}
import android.text.TextUtils
import android.util.Log
@@ -65,9 +65,11 @@ trait BaseService extends Service {
lazy val restartHanlder = new Handler(getMainLooper)
private var notification: ShadowsocksNotification = _
- private val closeReceiver: BroadcastReceiver = (context: Context, _: Intent) => {
- Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
- stopRunner(stopService = true)
+ private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => intent.getAction match {
+ case Action.RELOAD => forceLoad()
+ case _ =>
+ Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
+ stopRunner(stopService = true)
}
var closeReceiverRegistered: Boolean = _
@@ -103,20 +105,6 @@ trait BaseService extends Service {
stopListeningForBandwidth(cb) // saves an RPC, and safer
callbacks.unregister(cb)
}
-
- override def use(profileId: Int): Unit = synchronized(if (profileId < 0) stopRunner(stopService = true) else {
- val profile = app.profileManager.getProfile(profileId).orNull
- if (profile == null) stopRunner(stopService = true, getString(R.string.profile_empty)) else state match {
- case State.STOPPED => if (checkProfile(profile)) startRunner(profile)
- case State.CONNECTED => if (profileId != BaseService.this.profile.id && checkProfile(profile)) {
- stopRunner(stopService = false)
- startRunner(profile)
- }
- case _ => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + state)
- }
- })
-
- override def useSync(profileId: Int): Unit = use(profileId)
}
def checkProfile(profile: Profile): Boolean = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
@@ -124,6 +112,17 @@ trait BaseService extends Service {
false
} else true
+ def forceLoad(): Unit = app.currentProfile.orNull match {
+ case null => stopRunner(stopService = true, getString(R.string.profile_empty))
+ case p => if (checkProfile(p)) state match {
+ case State.STOPPED => startRunner()
+ case State.CONNECTED =>
+ stopRunner(stopService = false)
+ startRunner()
+ case s => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + s)
+ }
+ }
+
def connect() {
profile.name = profile.getName // save original name before it's (possibly) overwritten by IP addresses
@@ -161,38 +160,8 @@ trait BaseService extends Service {
}
def createNotification(): ShadowsocksNotification
- def startRunner(profile: Profile) {
- this.profile = profile
-
- if (Build.VERSION.SDK_INT >= 26) startForegroundService(new Intent(this, getClass))
+ def startRunner(): Unit = if (Build.VERSION.SDK_INT >= 26) startForegroundService(new Intent(this, getClass))
else startService(new Intent(this, getClass))
- 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
- }
-
- notification = createNotification()
- app.track(getClass.getSimpleName, "start")
-
- changeState(State.CONNECTING)
-
- Utils.ThrowableFuture(try connect() catch {
- case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
- case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
- case exc: Throwable =>
- stopRunner(stopService = true, getString(R.string.service_failed) + ": " + exc.getMessage)
- exc.printStackTrace()
- app.track(exc)
- })
- }
def stopRunner(stopService: Boolean, msg: String = null) {
// clean up recevier
@@ -281,7 +250,41 @@ trait BaseService extends Service {
}
// Service of shadowsocks should always be started explicitly
- override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_NOT_STICKY
+ override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
+ state match {
+ case State.STOPPED | State.IDLE =>
+ case _ => return Service.START_NOT_STICKY // ignore request
+ }
+ profile = app.currentProfile.orNull
+ TrafficMonitor.reset()
+ trafficMonitorThread = new TrafficMonitorThread(getApplicationContext)
+ trafficMonitorThread.start()
+
+ if (!closeReceiverRegistered) {
+ // register close receiver
+ val filter = new IntentFilter()
+ filter.addAction(Action.RELOAD)
+ filter.addAction(Intent.ACTION_SHUTDOWN)
+ filter.addAction(Action.CLOSE)
+ registerReceiver(closeReceiver, filter)
+ closeReceiverRegistered = true
+ }
+
+ notification = createNotification()
+ app.track(getClass.getSimpleName, "start")
+
+ changeState(State.CONNECTING)
+
+ Utils.ThrowableFuture(try connect() catch {
+ case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
+ case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
+ case exc: Throwable =>
+ stopRunner(stopService = true, getString(R.string.service_failed) + ": " + exc.getMessage)
+ exc.printStackTrace()
+ app.track(exc)
+ })
+ Service.START_NOT_STICKY
+ }
protected def changeState(s: Int, msg: String = null) {
val handler = new Handler(getMainLooper)
diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala
index 4908bf47..b3768b3b 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala
@@ -175,7 +175,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
})
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match {
- case Activity.RESULT_OK => bgService.use(app.dataStore.profileId)
+ case Activity.RESULT_OK => Utils.startSsService(this)
case _ => Log.e(TAG, "Failed to start VpnService")
}
@@ -299,8 +299,8 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
- fab.setOnClickListener(_ => if (state == State.CONNECTED) bgService.use(-1) else Utils.ThrowableFuture {
- if (app.isNatEnabled) bgService.use(app.dataStore.profileId) else {
+ fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture {
+ if (app.isNatEnabled) Utils.startSsService(this) else {
val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null))
@@ -369,7 +369,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
case DRAWER_PROFILES => displayFragment(new ProfilesFragment)
case DRAWER_RECOVERY =>
app.track("GlobalConfigFragment", "reset")
- if (bgService != null) bgService.use(-1)
+ Utils.stopSsService(this)
val dialog = ProgressDialog.show(this, "", getString(R.string.recovering), true, false)
val handler = new Handler {
override def handleMessage(msg: Message): Unit = if (dialog.isShowing && !isDestroyed) dialog.dismiss()
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala
index 2fd177c5..618a7931 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala
@@ -170,7 +170,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
app.switchProfile(item.id)
profilesAdapter.refreshId(old)
itemView.setSelected(true)
- if (activity.state == State.CONNECTED) activity.bgService.use(item.id) // reconnect to new profile
+ if (activity.state == State.CONNECTED) Utils.reloadSsService(activity)
}
override def onMenuItemClick(menu: MenuItem): Boolean = menu.getItemId match {
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
index a71ea95d..a1db6d74 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
@@ -24,11 +24,12 @@ import java.io.File
import java.net.{Inet6Address, InetAddress}
import java.util.Locale
+import android.app.Service
import android.content._
import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
-import com.github.shadowsocks.acl.{AclSyncJob, Acl}
+import com.github.shadowsocks.acl.{Acl, AclSyncJob}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
import eu.chainfire.libsuperuser.Shell
@@ -176,15 +177,17 @@ class ShadowsocksNatService extends BaseService {
su.addCommand((init_sb ++ http_sb).toArray)
}
- override def startRunner(profile: Profile): Unit = if (su == null)
+ override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) {
su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) =>
- if (exitCode == 0) super.startRunner(profile) else {
+ if (exitCode == 0) super.onStartCommand(intent, flags, startId) else {
if (su != null) {
su.close()
su = null
}
super.stopRunner(stopService = true, getString(R.string.nat_no_root))
})
+ Service.START_NOT_STICKY
+ } else super.onStartCommand(intent, flags, startId)
override def connect() {
super.connect()
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
index 197cc935..749560be 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
@@ -26,6 +26,7 @@ import android.net.VpnService
import android.os.{Bundle, Handler}
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
+import com.github.shadowsocks.utils.Utils
object ShadowsocksRunnerActivity {
private final val TAG = "ShadowsocksRunnerActivity"
@@ -46,7 +47,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
def startBackgroundService() {
if (app.isNatEnabled) {
- bgService.use(app.dataStore.profileId)
+ Utils.startSsService(this)
finish()
} else {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
@@ -87,10 +88,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
resultCode match {
- case Activity.RESULT_OK =>
- if (bgService != null) {
- bgService.use(app.dataStore.profileId)
- }
+ case Activity.RESULT_OK => Utils.startSsService(this)
case _ =>
Log.e(TAG, "Failed to start VpnService")
}
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
deleted file mode 100644
index e9079c46..00000000
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
+++ /dev/null
@@ -1,65 +0,0 @@
-/*******************************************************************************/
-/* */
-/* Copyright (C) 2017 by Max Lv */
-/* Copyright (C) 2017 by Mygod Studio */
-/* */
-/* 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 . */
-/* */
-/*******************************************************************************/
-
-package com.github.shadowsocks
-
-import android.app.Service
-import android.content.Intent
-import android.net.VpnService
-import android.os.{Build, Handler, IBinder}
-import android.support.v4.app.NotificationCompat
-import android.support.v4.os.BuildCompat
-import com.github.shadowsocks.ShadowsocksApplication.app
-
-class ShadowsocksRunnerService extends Service with ServiceBoundContext {
- val handler = new Handler()
-
- override def onBind(intent: Intent): IBinder = {
- null
- }
-
- override def onServiceConnected() {
- handler.postDelayed(() => {
- if (bgService != null) {
- if (app.isNatEnabled) startBackgroundService()
- else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
- handler.postDelayed(() => stopSelf(), 3000)
- }
- }, 1000)
- }
-
- def startBackgroundService(): Unit = bgService.useSync(app.dataStore.profileId)
-
- override def onCreate() {
- super.onCreate()
- if (Build.VERSION.SDK_INT >= 26) {
- val builder = new NotificationCompat.Builder(this)
- builder.setPriority(NotificationCompat.PRIORITY_MIN)
- startForeground(1, builder.build)
- }
- attachService()
- }
-
- override def onDestroy() {
- super.onDestroy()
- stopForeground(true)
- detachService()
- }
-}
diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
index 5e4d1e34..1f124776 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
@@ -23,6 +23,7 @@ package com.github.shadowsocks
import java.io.File
import java.util.Locale
+import android.app.Service
import android.content._
import android.content.pm.PackageManager.NameNotFoundException
import android.net.VpnService
@@ -30,7 +31,6 @@ import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet}
-import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
import scala.collection.mutable.ArrayBuffer
@@ -100,18 +100,15 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}
}
- override def startRunner(profile: Profile) {
-
+ override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
// ensure the VPNService is prepared
if (VpnService.prepare(this) != null) {
val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(i)
stopRunner(stopService = true)
- return
- }
-
- super.startRunner(profile)
+ Service.START_NOT_STICKY
+ } else super.onStartCommand(intent, flags, startId)
}
override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-vpn")
diff --git a/mobile/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala b/mobile/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala
index b015eb65..fa9017b8 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala
@@ -30,10 +30,17 @@ import com.github.shadowsocks.ShadowsocksApplication.app
class TaskerReceiver extends BroadcastReceiver {
override def onReceive(context: Context, intent: Intent) {
val settings = TaskerSettings.fromIntent(intent)
+ var changed = false
app.profileManager.getProfile(settings.profileId) match {
- case Some(_) => app.switchProfile(settings.profileId)
+ case Some(_) => if (app.dataStore.profileId != settings.profileId) {
+ app.switchProfile(settings.profileId)
+ changed = true
+ }
case _ =>
}
- if (settings.switchOn) Utils.startSsService(context) else Utils.stopSsService(context)
+ if (settings.switchOn) {
+ Utils.startSsService(context)
+ if (changed) Utils.reloadSsService(context)
+ } else Utils.stopSsService(context)
}
}
diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala
index 80bd0f4a..e7e30a92 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala
@@ -94,6 +94,7 @@ object State {
object Action {
final val SERVICE = "com.github.shadowsocks.SERVICE"
final val CLOSE = "com.github.shadowsocks.CLOSE"
+ final val RELOAD = "com.github.shadowsocks.RELOAD"
final val EXTRA_PROFILE_ID = "com.github.shadowsocks.EXTRA_PROFILE_ID"
}
diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala
index 5bda5098..eae40a19 100644
--- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala
+++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala
@@ -33,7 +33,7 @@ import android.view.View.MeasureSpec
import android.view.{Gravity, View, Window}
import android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app
-import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
+import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService}
import org.xbill.DNS._
import scala.collection.JavaConversions._
@@ -137,11 +137,11 @@ object Utils {
}
def startSsService(context: Context) {
- val intent = new Intent(context, classOf[ShadowsocksRunnerService])
- if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent)
- else context.startService(intent)
+ val intent =
+ new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService])
+ if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent)
}
-
+ def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD))
def stopSsService(context: Context) {
val intent = new Intent(Action.CLOSE)
context.sendBroadcast(intent)