Commit 143afb05 authored by Mygod's avatar Mygod

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.
parent 27fc2734
......@@ -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")
}
......
/*******************************************************************************/
/* */
/* 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)
......
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