/******************************************************************************
* *
* Copyright (C) 2021 by nekohasekai
*
* Copyright (C) 2021 by Max Lv *
* Copyright (C) 2021 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 io.nekohasekai.sagernet.bg
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.RemoteException
import io.nekohasekai.sagernet.Action
import io.nekohasekai.sagernet.Key
import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.aidl.*
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.ktx.runOnMainDispatcher
class SagerConnection(private var listenForDeath: Boolean = false) : ServiceConnection,
IBinder.DeathRecipient {
companion object {
val serviceClass
get() = when (DataStore.serviceMode) {
Key.MODE_PROXY -> ProxyService::class
Key.MODE_VPN -> VpnService::class // Key.MODE_TRANS -> TransproxyService::class
else -> throw UnknownError()
}.java
}
interface Callback {
fun stateChanged(state: BaseService.State, profileName: String?, msg: String?)
fun trafficUpdated(profileId: Long, stats: TrafficStats, isCurrent: Boolean) {}
fun statsUpdated(stats: List) {}
fun observatoryResultsUpdated(groupId: Long) {}
fun profilePersisted(profileId: Long) {}
fun missingPlugin(profileName: String, pluginName: String) {}
fun routeAlert(type: Int, routeName: String) {}
fun onServiceConnected(service: ISagerNetService)
/**
* Different from Android framework, this method will be called even when you call `detachService`.
*/
fun onServiceDisconnected() {}
fun onBinderDied() {}
}
private var connectionActive = false
private var callbackRegistered = false
private var callback: Callback? = null
private val serviceCallback = object : ISagerNetServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
val s = BaseService.State.entries[state]
SagerNet.started = s.canStop
val callback = callback ?: return
runOnMainDispatcher {
callback.stateChanged(s, profileName, msg)
}
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats, isCurrent: Boolean) {
val callback = callback ?: return
runOnMainDispatcher {
callback.trafficUpdated(profileId, stats, isCurrent)
}
}
override fun profilePersisted(profileId: Long) {
val callback = callback ?: return
runOnMainDispatcher { callback.profilePersisted(profileId) }
}
override fun missingPlugin(profileName: String, pluginName: String) {
val callback = callback ?: return
runOnMainDispatcher {
callback.missingPlugin(profileName, pluginName)
}
}
override fun statsUpdated(statsList: AppStatsList) {
val callback = callback ?: return
callback.statsUpdated(statsList.data)
}
override fun routeAlert(type: Int, routeName: String) {
val callback = callback ?: return
runOnMainDispatcher {
callback.routeAlert(type, routeName)
}
}
override fun observatoryResultsUpdated(groupId: Long) {
val callback = callback ?: return
runOnMainDispatcher {
callback.observatoryResultsUpdated(groupId)
}
}
}
private var binder: IBinder? = null
var bandwidthTimeout = 0L
set(value) {
try {
if (value > 0) service?.startListeningForBandwidth(serviceCallback, value)
else service?.stopListeningForBandwidth(serviceCallback)
} catch (_: RemoteException) {
}
field = value
}
var trafficTimeout = 0L
set(value) {
try {
if (value > 0) service?.startListeningForStats(serviceCallback, value)
else service?.stopListeningForStats(serviceCallback)
} catch (_: RemoteException) {
}
field = value
}
var service: ISagerNetService? = null
override fun onServiceConnected(name: ComponentName?, binder: IBinder) {
this.binder = binder
val service = ISagerNetService.Stub.asInterface(binder)!!
this.service = service
try {
if (listenForDeath) binder.linkToDeath(this, 0)
check(!callbackRegistered)
service.registerCallback(serviceCallback)
callbackRegistered = true
if (bandwidthTimeout > 0) service.startListeningForBandwidth(
serviceCallback, bandwidthTimeout
)
if (trafficTimeout > 0) service.startListeningForStats(
serviceCallback, trafficTimeout
)
} catch (e: RemoteException) {
e.printStackTrace()
}
callback!!.onServiceConnected(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
unregisterCallback()
callback?.onServiceDisconnected()
service = null
binder = null
}
override fun binderDied() {
service = null
callbackRegistered = false
callback?.also { runOnMainDispatcher { it.onBinderDied() } }
}
private fun unregisterCallback() {
val service = service
if (service != null && callbackRegistered) try {
service.unregisterCallback(serviceCallback)
} catch (_: RemoteException) {
}
callbackRegistered = false
}
fun connect(context: Context, callback: Callback) {
if (connectionActive) return
connectionActive = true
check(this.callback == null)
this.callback = callback
val intent = Intent(context, serviceClass).setAction(Action.SERVICE)
context.bindService(intent, this, Context.BIND_AUTO_CREATE)
}
fun disconnect(context: Context) {
unregisterCallback()
if (connectionActive) try {
context.unbindService(this)
} catch (_: IllegalArgumentException) {
} // ignore
connectionActive = false
if (listenForDeath) try {
binder?.unlinkToDeath(this, 0)
} catch (_: NoSuchElementException) {
}
binder = null
try {
service?.stopListeningForBandwidth(serviceCallback)
service?.stopListeningForStats(serviceCallback)
} catch (_: RemoteException) {
}
service = null
callback = null
}
}