Commit 32aa8007 authored by Mygod's avatar Mygod

Fix memory leaks caused by writeStrongBinder

parent fcdcbd14
...@@ -6,8 +6,8 @@ interface IShadowsocksService { ...@@ -6,8 +6,8 @@ interface IShadowsocksService {
int getState(); int getState();
String getProfileName(); String getProfileName();
oneway void registerCallback(IShadowsocksServiceCallback cb); void registerCallback(in IShadowsocksServiceCallback cb);
oneway void startListeningForBandwidth(IShadowsocksServiceCallback cb); void startListeningForBandwidth(in IShadowsocksServiceCallback cb);
oneway void stopListeningForBandwidth(IShadowsocksServiceCallback cb); oneway void stopListeningForBandwidth(in IShadowsocksServiceCallback cb);
oneway void unregisterCallback(IShadowsocksServiceCallback cb); oneway void unregisterCallback(in IShadowsocksServiceCallback cb);
} }
...@@ -2,9 +2,9 @@ package com.github.shadowsocks.aidl; ...@@ -2,9 +2,9 @@ package com.github.shadowsocks.aidl;
import com.github.shadowsocks.aidl.TrafficStats; import com.github.shadowsocks.aidl.TrafficStats;
interface IShadowsocksServiceCallback { oneway interface IShadowsocksServiceCallback {
oneway void stateChanged(int state, String profileName, String msg); void stateChanged(int state, String profileName, String msg);
oneway void trafficUpdated(long profileId, in TrafficStats stats); void trafficUpdated(long profileId, in TrafficStats stats);
// Traffic data has persisted to database, listener should refetch their data from database // Traffic data has persisted to database, listener should refetch their data from database
oneway void trafficPersisted(long profileId); void trafficPersisted(long profileId);
} }
...@@ -42,6 +42,7 @@ import androidx.work.Configuration ...@@ -42,6 +42,7 @@ import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
...@@ -140,7 +141,7 @@ object Core { ...@@ -140,7 +141,7 @@ object Core {
if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES)!! else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES)!!
fun startService() = ContextCompat.startForegroundService(app, Intent(app, BaseService.serviceClass.java)) fun startService() = ContextCompat.startForegroundService(app, Intent(app, ShadowsocksConnection.serviceClass))
fun reloadService() = app.sendBroadcast(Intent(Action.RELOAD)) fun reloadService() = app.sendBroadcast(Intent(Action.RELOAD))
fun stopService() = app.sendBroadcast(Intent(Action.CLOSE)) fun stopService() = app.sendBroadcast(Intent(Action.CLOSE))
......
...@@ -31,33 +31,32 @@ import android.widget.Toast ...@@ -31,33 +31,32 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.broadcastReceiver import com.github.shadowsocks.utils.broadcastReceiver
class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Callback { class VpnRequestActivity : AppCompatActivity() {
companion object { companion object {
private const val TAG = "VpnRequestActivity" private const val TAG = "VpnRequestActivity"
private const val REQUEST_CONNECT = 1 private const val REQUEST_CONNECT = 1
} }
private val connection = ShadowsocksConnection(this)
private var receiver: BroadcastReceiver? = null private var receiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!BaseService.usingVpnMode) { if (DataStore.serviceMode != Key.modeVpn) {
finish() finish()
return return
} }
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) { if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
receiver = broadcastReceiver { _, _ -> connection.connect(this) } receiver = broadcastReceiver { _, _ -> request() }
registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT)) registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT))
} else connection.connect(this) } else request()
} }
override fun onServiceConnected(service: IShadowsocksService) { private fun request() {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null) if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null)
else startActivityForResult(intent, REQUEST_CONNECT) else startActivityForResult(intent, REQUEST_CONNECT)
...@@ -73,7 +72,6 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Callback { ...@@ -73,7 +72,6 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Callback {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
connection.disconnect(this)
if (receiver != null) unregisterReceiver(receiver) if (receiver != null) unregisterReceiver(receiver)
} }
} }
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks package com.github.shadowsocks.aidl
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
...@@ -27,15 +27,31 @@ import android.content.ServiceConnection ...@@ -27,15 +27,31 @@ import android.content.ServiceConnection
import android.os.DeadObjectException import android.os.DeadObjectException
import android.os.IBinder import android.os.IBinder
import android.os.RemoteException import android.os.RemoteException
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.Core
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.bg.ProxyService
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.TransproxyService
import com.github.shadowsocks.bg.VpnService
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.Key
/**
* This object should be compact as it will not get GC-ed.
*/
class ShadowsocksConnection(private var listenForDeath: Boolean = false) : ServiceConnection, IBinder.DeathRecipient {
companion object {
val serviceClass get() = when (DataStore.serviceMode) {
Key.modeProxy -> ProxyService::class
Key.modeVpn -> VpnService::class
Key.modeTransproxy -> TransproxyService::class
else -> throw UnknownError()
}.java
}
class ShadowsocksConnection(private val callback: Callback, private var listenForDeath: Boolean = false) :
ServiceConnection, IBinder.DeathRecipient {
interface Callback { interface Callback {
val serviceCallback: IShadowsocksServiceCallback? get() = null fun stateChanged(state: Int, profileName: String?, msg: String?)
fun trafficUpdated(profileId: Long, stats: TrafficStats) { }
fun trafficPersisted(profileId: Long) { }
fun onServiceConnected(service: IShadowsocksService) fun onServiceConnected(service: IShadowsocksService)
/** /**
...@@ -47,14 +63,26 @@ class ShadowsocksConnection(private val callback: Callback, private var listenFo ...@@ -47,14 +63,26 @@ class ShadowsocksConnection(private val callback: Callback, private var listenFo
private var connectionActive = false private var connectionActive = false
private var callbackRegistered = false private var callbackRegistered = false
private var callback: Callback? = null
private val serviceCallback = object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
Core.handler.post { callback!!.stateChanged(state, profileName, msg) }
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
Core.handler.post { callback!!.trafficUpdated(profileId, stats) }
}
override fun trafficPersisted(profileId: Long) {
Core.handler.post { callback!!.trafficPersisted(profileId) }
}
}
private var binder: IBinder? = null private var binder: IBinder? = null
var listeningForBandwidth = false var listeningForBandwidth = false
set(value) { set(value) {
val service = service val service = service
if (listeningForBandwidth != value && service != null && callback.serviceCallback != null) if (listeningForBandwidth != value && service != null)
if (value) service.startListeningForBandwidth(callback.serviceCallback) else try { if (value) service.startListeningForBandwidth(serviceCallback) else try {
service.stopListeningForBandwidth(callback.serviceCallback) service.stopListeningForBandwidth(serviceCallback)
} catch (_: DeadObjectException) { } } catch (_: DeadObjectException) { }
field = value field = value
} }
...@@ -65,51 +93,53 @@ class ShadowsocksConnection(private val callback: Callback, private var listenFo ...@@ -65,51 +93,53 @@ class ShadowsocksConnection(private val callback: Callback, private var listenFo
if (listenForDeath) binder.linkToDeath(this, 0) if (listenForDeath) binder.linkToDeath(this, 0)
val service = IShadowsocksService.Stub.asInterface(binder)!! val service = IShadowsocksService.Stub.asInterface(binder)!!
this.service = service this.service = service
if (callback.serviceCallback != null && !callbackRegistered) try { if (!callbackRegistered) try {
service.registerCallback(callback.serviceCallback) service.registerCallback(serviceCallback)
callbackRegistered = true callbackRegistered = true
if (listeningForBandwidth) service.startListeningForBandwidth(callback.serviceCallback) if (listeningForBandwidth) service.startListeningForBandwidth(serviceCallback)
} catch (_: RemoteException) { } } catch (_: RemoteException) { }
callback.onServiceConnected(service) callback!!.onServiceConnected(service)
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
unregisterCallback() unregisterCallback()
callback.onServiceDisconnected() callback!!.onServiceDisconnected()
service = null service = null
binder = null binder = null
} }
override fun binderDied() { override fun binderDied() {
service = null service = null
callback.onBinderDied() callback!!.onBinderDied()
} }
private fun unregisterCallback() { private fun unregisterCallback() {
val service = service val service = service
if (service != null && callback.serviceCallback != null && callbackRegistered) try { if (service != null && callbackRegistered) try {
service.unregisterCallback(callback.serviceCallback) service.unregisterCallback(serviceCallback)
} catch (_: RemoteException) { } } catch (_: RemoteException) { }
callbackRegistered = false callbackRegistered = false
} }
fun connect(context: Context) { fun connect(context: Context, callback: Callback) {
if (connectionActive) return if (connectionActive) return
connectionActive = true connectionActive = true
val intent = Intent(context, BaseService.serviceClass.java).setAction(Action.SERVICE) check(this.callback == null)
this.callback = callback
val intent = Intent(context, serviceClass).setAction(Action.SERVICE)
context.bindService(intent, this, Context.BIND_AUTO_CREATE) context.bindService(intent, this, Context.BIND_AUTO_CREATE)
} }
fun disconnect(context: Context) { fun disconnect(context: Context) {
unregisterCallback() unregisterCallback()
callback.onServiceDisconnected()
if (connectionActive) try { if (connectionActive) try {
context.unbindService(this) context.unbindService(this)
} catch (_: IllegalArgumentException) { } // ignore } catch (_: IllegalArgumentException) { } // ignore
connectionActive = false connectionActive = false
if (listenForDeath) binder?.unlinkToDeath(this, 0) if (listenForDeath) binder?.unlinkToDeath(this, 0)
binder = null binder = null
if (callback.serviceCallback != null) service?.stopListeningForBandwidth(callback.serviceCallback) service?.stopListeningForBandwidth(serviceCallback)
service = null service = null
callback = null
} }
} }
...@@ -59,7 +59,7 @@ object BaseService { ...@@ -59,7 +59,7 @@ object BaseService {
const val CONFIG_FILE = "shadowsocks.conf" const val CONFIG_FILE = "shadowsocks.conf"
const val CONFIG_FILE_UDP = "shadowsocks-udp.conf" const val CONFIG_FILE_UDP = "shadowsocks-udp.conf"
class Data internal constructor(private val service: Interface) { class Data internal constructor(private val service: Interface): AutoCloseable {
@Volatile var state = STOPPED @Volatile var state = STOPPED
val processes = GuardedProcessPool() val processes = GuardedProcessPool()
@Volatile var proxy: ProxyInstance? = null @Volatile var proxy: ProxyInstance? = null
...@@ -160,6 +160,8 @@ object BaseService { ...@@ -160,6 +160,8 @@ object BaseService {
} }
state = s state = s
} }
override fun close() = callbacks.kill()
} }
interface Interface { interface Interface {
val tag: String val tag: String
...@@ -317,6 +319,10 @@ object BaseService { ...@@ -317,6 +319,10 @@ object BaseService {
} }
return Service.START_NOT_STICKY return Service.START_NOT_STICKY
} }
fun onDestroy() {
data.close()
}
} }
private val instances = WeakHashMap<Interface, Data>() private val instances = WeakHashMap<Interface, Data>()
...@@ -324,12 +330,4 @@ object BaseService { ...@@ -324,12 +330,4 @@ object BaseService {
instances[instance] = Data(instance) instances[instance] = Data(instance)
RemoteConfig.fetch() RemoteConfig.fetch()
} }
val usingVpnMode: Boolean get() = DataStore.serviceMode == Key.modeVpn
val serviceClass get() = when (DataStore.serviceMode) {
Key.modeProxy -> ProxyService::class
Key.modeVpn -> VpnService::class
Key.modeTransproxy -> TransproxyService::class
else -> throw UnknownError()
}
} }
...@@ -38,4 +38,8 @@ class ProxyService : Service(), BaseService.Interface { ...@@ -38,4 +38,8 @@ class ProxyService : Service(), BaseService.Interface {
override fun onBind(intent: Intent) = super.onBind(intent) override fun onBind(intent: Intent) = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<BaseService.Interface>.onStartCommand(intent, flags, startId) super<BaseService.Interface>.onStartCommand(intent, flags, startId)
override fun onDestroy() {
super<Service>.onDestroy()
super<BaseService.Interface>.onDestroy()
}
} }
...@@ -77,4 +77,9 @@ redsocks { ...@@ -77,4 +77,9 @@ redsocks {
super.startNativeProcesses() super.startNativeProcesses()
if (data.proxy!!.profile.udpdns) startDNSTunnel() if (data.proxy!!.profile.udpdns) startDNSTunnel()
} }
override fun onDestroy() {
super<Service>.onDestroy()
super<LocalDnsService.Interface>.onDestroy()
}
} }
...@@ -35,6 +35,7 @@ import com.github.shadowsocks.VpnRequestActivity ...@@ -35,6 +35,7 @@ import com.github.shadowsocks.VpnRequestActivity
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.Subnet import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.parseNumericAddress import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
...@@ -160,7 +161,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -160,7 +161,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (BaseService.usingVpnMode) if (DataStore.serviceMode == Key.modeVpn)
if (BaseVpnService.prepare(this) != null) if (BaseVpnService.prepare(this) != null)
startActivity(Intent(this, VpnRequestActivity::class.java) startActivity(Intent(this, VpnRequestActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
...@@ -279,4 +280,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -279,4 +280,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
tries += 1 tries += 1
} }
} }
override fun onDestroy() {
super<BaseVpnService>.onDestroy()
super<LocalDnsService.Interface>.onDestroy()
}
} }
...@@ -84,7 +84,7 @@ class HttpsTest : ViewModel() { ...@@ -84,7 +84,7 @@ class HttpsTest : ViewModel() {
Acl.CHINALIST -> "www.qualcomm.cn" Acl.CHINALIST -> "www.qualcomm.cn"
else -> "www.google.com" else -> "www.google.com"
}, "/generate_204") }, "/generate_204")
val conn = (if (BaseService.usingVpnMode) url.openConnection() else val conn = (if (DataStore.serviceMode == Key.modeVpn) url.openConnection() else
url.openConnection(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", DataStore.portProxy)))) url.openConnection(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", DataStore.portProxy))))
as HttpURLConnection as HttpURLConnection
conn.setRequestProperty("Connection", "close") conn.setRequestProperty("Connection", "close")
......
...@@ -45,7 +45,7 @@ import androidx.preference.PreferenceDataStore ...@@ -45,7 +45,7 @@ import androidx.preference.PreferenceDataStore
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.CustomRulesFragment import com.github.shadowsocks.acl.CustomRulesFragment
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.aidl.TrafficStats import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.bg.Executable import com.github.shadowsocks.bg.Executable
...@@ -92,12 +92,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -92,12 +92,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
// service // service
var state = BaseService.IDLE var state = BaseService.IDLE
override val serviceCallback = object : IShadowsocksServiceCallback.Stub() { override fun stateChanged(state: Int, profileName: String?, msg: String?) = changeState(state, msg, true)
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
Core.handler.post { changeState(state, msg, true) }
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
Core.handler.post {
if (profileId == 0L) this@MainActivity.stats.updateTraffic( if (profileId == 0L) this@MainActivity.stats.updateTraffic(
stats.txRate, stats.rxRate, stats.txTotal, stats.rxTotal) stats.txRate, stats.rxRate, stats.txTotal, stats.rxTotal)
if (state != BaseService.STOPPING) { if (state != BaseService.STOPPING) {
...@@ -105,10 +101,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -105,10 +101,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
?.onTrafficUpdated(profileId, stats) ?.onTrafficUpdated(profileId, stats)
} }
} }
}
override fun trafficPersisted(profileId: Long) { override fun trafficPersisted(profileId: Long) {
Core.handler.post { ProfilesFragment.instance?.onTrafficPersisted(profileId) } ProfilesFragment.instance?.onTrafficPersisted(profileId)
}
} }
private fun changeState(state: Int, msg: String? = null, animate: Boolean = false) { private fun changeState(state: Int, msg: String? = null, animate: Boolean = false) {
...@@ -122,7 +116,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -122,7 +116,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
private fun toggle() = when { private fun toggle() = when {
state == BaseService.CONNECTED -> Core.stopService() state == BaseService.CONNECTED -> Core.stopService()
BaseService.usingVpnMode -> { DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
...@@ -130,7 +124,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -130,7 +124,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
else -> Core.startService() else -> Core.startService()
} }
private val connection = ShadowsocksConnection(this, true) private val connection = ShadowsocksConnection(true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try { override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
service.state service.state
} catch (_: DeadObjectException) { } catch (_: DeadObjectException) {
...@@ -141,7 +135,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -141,7 +135,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
Core.handler.post { Core.handler.post {
connection.disconnect(this) connection.disconnect(this)
Executable.killAll() Executable.killAll()
connection.connect(this) connection.connect(this, this)
} }
} }
...@@ -173,7 +167,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -173,7 +167,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
fab.setOnClickListener { toggle() } fab.setOnClickListener { toggle() }
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.IDLE) // reset everything to init state
Core.handler.post { connection.connect(this) } Core.handler.post { connection.connect(this, this) }
DataStore.publicStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
val intent = this.intent val intent = this.intent
...@@ -213,7 +207,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -213,7 +207,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
when (key) { when (key) {
Key.serviceMode -> Core.handler.post { Key.serviceMode -> Core.handler.post {
connection.disconnect(this) connection.disconnect(this)
connection.connect(this) connection.connect(this, this)
} }
} }
} }
......
...@@ -30,10 +30,13 @@ import androidx.core.content.pm.ShortcutInfoCompat ...@@ -30,10 +30,13 @@ import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
@Suppress("DEPRECATION")
@Deprecated("This shortcut is inefficient and should be superseded by TileService for API 24+.")
class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback { class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback {
private val connection = ShadowsocksConnection(this) private val connection = ShadowsocksConnection()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -46,7 +49,7 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback { ...@@ -46,7 +49,7 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback {
.build())) .build()))
finish() finish()
} else { } else {
connection.connect(this) connection.connect(this, this)
if (Build.VERSION.SDK_INT >= 25) getSystemService<ShortcutManager>()!!.reportShortcutUsed("toggle") if (Build.VERSION.SDK_INT >= 25) getSystemService<ShortcutManager>()!!.reportShortcutUsed("toggle")
} }
} }
...@@ -59,6 +62,8 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback { ...@@ -59,6 +62,8 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback {
finish() finish()
} }
override fun stateChanged(state: Int, profileName: String?, msg: String?) { }
override fun onDestroy() { override fun onDestroy() {
connection.disconnect(this) connection.disconnect(this)
super.onDestroy() super.onDestroy()
......
...@@ -23,16 +23,14 @@ package com.github.shadowsocks.bg ...@@ -23,16 +23,14 @@ package com.github.shadowsocks.bg
import android.app.KeyguardManager import android.app.KeyguardManager
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.service.quicksettings.Tile import android.service.quicksettings.Tile
import android.service.quicksettings.TileService as BaseTileService
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.R import com.github.shadowsocks.R
import com.github.shadowsocks.ShadowsocksConnection
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import android.service.quicksettings.TileService as BaseTileService
@RequiresApi(24) @RequiresApi(24)
class TileService : BaseTileService(), ShadowsocksConnection.Callback { class TileService : BaseTileService(), ShadowsocksConnection.Callback {
...@@ -42,35 +40,10 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -42,35 +40,10 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
private val keyguard by lazy { getSystemService<KeyguardManager>()!! } private val keyguard by lazy { getSystemService<KeyguardManager>()!! }
private var tapPending = false private var tapPending = false
private val connection = ShadowsocksConnection(this) private val connection = ShadowsocksConnection()
override val serviceCallback = object : IShadowsocksServiceCallback.Stub() { override fun stateChanged(state: Int, profileName: String?, msg: String?) = updateTile(state) { profileName }
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
val tile = qsTile ?: return
var label: String? = null
when (state) {
BaseService.STOPPED -> {
tile.icon = iconIdle
tile.state = Tile.STATE_INACTIVE
}
BaseService.CONNECTED -> {
tile.icon = iconConnected
if (!keyguard.isDeviceLocked) label = profileName
tile.state = Tile.STATE_ACTIVE
}
else -> {
tile.icon = iconBusy
tile.state = Tile.STATE_UNAVAILABLE
}
}
tile.label = label ?: getString(R.string.app_name)
tile.updateTile()
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { }
override fun trafficPersisted(profileId: Long) { }
}
override fun onServiceConnected(service: IShadowsocksService) { override fun onServiceConnected(service: IShadowsocksService) {
serviceCallback.stateChanged(service.state, service.profileName, null) updateTile(service.state) { service.profileName }
if (tapPending) { if (tapPending) {
tapPending = false tapPending = false
onClick() onClick()
...@@ -79,7 +52,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -79,7 +52,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
override fun onStartListening() { override fun onStartListening() {
super.onStartListening() super.onStartListening()
connection.connect(this) connection.connect(this, this)
} }
override fun onStopListening() { override fun onStopListening() {
connection.disconnect(this) connection.disconnect(this)
...@@ -90,6 +63,29 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -90,6 +63,29 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle() if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle()
} }
private fun updateTile(serviceState: Int, profileName: () -> String?) {
qsTile?.apply {
label = null
when (serviceState) {
BaseService.STOPPED -> {
icon = iconIdle
state = Tile.STATE_INACTIVE
}
BaseService.CONNECTED -> {
icon = iconConnected
if (!keyguard.isDeviceLocked) label = profileName()
state = Tile.STATE_ACTIVE
}
else -> {
icon = iconBusy
state = Tile.STATE_UNAVAILABLE
}
}
label = label ?: getString(R.string.app_name)
updateTile()
}
}
private fun toggle() { private fun toggle() {
val service = connection.service val service = connection.service
if (service == null) tapPending = true else when (service.state) { if (service == null) tapPending = true else when (service.state) {
......
...@@ -42,9 +42,8 @@ import androidx.preference.SwitchPreference ...@@ -42,9 +42,8 @@ import androidx.preference.SwitchPreference
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.BootReceiver import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.ShadowsocksConnection
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.aidl.TrafficStats import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.bg.Executable import com.github.shadowsocks.bg.Executable
...@@ -89,10 +88,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -89,10 +88,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
// service // service
var state = BaseService.IDLE var state = BaseService.IDLE
private set private set
override val serviceCallback = object : IShadowsocksServiceCallback.Stub() { override fun stateChanged(state: Int, profileName: String?, msg: String?) = changeState(state, msg)
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
Core.handler.post { changeState(state, msg) }
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
if (profileId == 0L) this@MainPreferenceFragment.stats.summary = getString(R.string.stat_summary, if (profileId == 0L) this@MainPreferenceFragment.stats.summary = getString(R.string.stat_summary,
getString(R.string.speed, Formatter.formatFileSize(activity, stats.txRate)), getString(R.string.speed, Formatter.formatFileSize(activity, stats.txRate)),
...@@ -100,8 +96,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -100,8 +96,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
Formatter.formatFileSize(activity, stats.txTotal), Formatter.formatFileSize(activity, stats.txTotal),
Formatter.formatFileSize(activity, stats.rxTotal)) Formatter.formatFileSize(activity, stats.rxTotal))
} }
override fun trafficPersisted(profileId: Long) { }
}
private fun changeState(state: Int, msg: String? = null) { private fun changeState(state: Int, msg: String? = null) {
fab.isEnabled = state == BaseService.STOPPED || state == BaseService.CONNECTED fab.isEnabled = state == BaseService.STOPPED || state == BaseService.CONNECTED
...@@ -115,7 +109,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -115,7 +109,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
stats.isVisible = state == BaseService.CONNECTED stats.isVisible = state == BaseService.CONNECTED
val owner = activity as FragmentActivity // TODO: change to this when refactored to androidx val owner = activity as FragmentActivity // TODO: change to this when refactored to androidx
if (state != BaseService.CONNECTED) { if (state != BaseService.CONNECTED) {
serviceCallback.trafficUpdated(0, TrafficStats()) trafficUpdated(0, TrafficStats())
tester.status.removeObservers(owner) tester.status.removeObservers(owner)
if (state != BaseService.IDLE) tester.invalidate() if (state != BaseService.IDLE) tester.invalidate()
} else tester.status.observe(owner, Observer { } else tester.status.observe(owner, Observer {
...@@ -141,7 +135,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -141,7 +135,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
} }
} }
private val connection = ShadowsocksConnection(this, true) private val connection = ShadowsocksConnection(true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try { override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
service.state service.state
} catch (_: DeadObjectException) { } catch (_: DeadObjectException) {
...@@ -152,7 +146,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -152,7 +146,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
Core.handler.post { Core.handler.post {
connection.disconnect(activity) connection.disconnect(activity)
Executable.killAll() Executable.killAll()
connection.connect(activity) connection.connect(activity, this)
} }
} }
...@@ -202,7 +196,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -202,7 +196,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
tester = ViewModelProviders.of(activity as FragmentActivity).get() tester = ViewModelProviders.of(activity as FragmentActivity).get()
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.IDLE) // reset everything to init state
connection.connect(activity) connection.connect(activity, this)
DataStore.publicStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
} }
...@@ -227,7 +221,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -227,7 +221,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
fun startService() { fun startService() {
when { when {
state != BaseService.STOPPED -> return state != BaseService.STOPPED -> return
BaseService.usingVpnMode -> { DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(activity) val intent = VpnService.prepare(activity)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
...@@ -240,7 +234,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti ...@@ -240,7 +234,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
when (key) { when (key) {
Key.serviceMode -> Core.handler.post { Key.serviceMode -> Core.handler.post {
connection.disconnect(activity) connection.disconnect(activity)
connection.connect(activity) connection.connect(activity, this)
} }
} }
} }
......
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