owenclave / app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * 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 } }