Use startService calls instead of AIDL to start services
Because our data store is process-safe, we can remove the `use` function entirely and move more components to :bg process.
This commit is contained in:
@@ -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">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".ShadowsocksRunnerService"
|
||||
android:exported="false">
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ShadowsocksNatService"
|
||||
android:process=":bg"
|
||||
@@ -134,13 +130,13 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name=".BootReceiver" android:enabled="false">
|
||||
<receiver android:name=".BootReceiver" android:process=":bg" android:enabled="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".TaskerReceiver" tools:ignore="ExportedReceiver">
|
||||
<receiver android:name=".TaskerReceiver" android:process=":bg" tools:ignore="ExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
|
||||
</intent-filter>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*******************************************************************************/
|
||||
/* */
|
||||
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
|
||||
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
|
||||
/* */
|
||||
/* 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/>. */
|
||||
/* */
|
||||
/*******************************************************************************/
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user