Commit dca0da09 authored by Mygod's avatar Mygod

Allow stop service while connecting

parent cf0ad396
...@@ -28,6 +28,7 @@ import android.os.DeadObjectException ...@@ -28,6 +28,7 @@ import android.os.DeadObjectException
import android.os.Handler import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.RemoteException import android.os.RemoteException
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.bg.ProxyService import com.github.shadowsocks.bg.ProxyService
import com.github.shadowsocks.bg.TransproxyService import com.github.shadowsocks.bg.TransproxyService
import com.github.shadowsocks.bg.VpnService import com.github.shadowsocks.bg.VpnService
...@@ -51,7 +52,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(), ...@@ -51,7 +52,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(),
} }
interface Callback { interface Callback {
fun stateChanged(state: Int, profileName: String?, msg: String?) fun stateChanged(state: BaseService.State, profileName: String?, msg: String?)
fun trafficUpdated(profileId: Long, stats: TrafficStats) { } fun trafficUpdated(profileId: Long, stats: TrafficStats) { }
fun trafficPersisted(profileId: Long) { } fun trafficPersisted(profileId: Long) { }
...@@ -69,7 +70,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(), ...@@ -69,7 +70,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(),
private val serviceCallback = object : IShadowsocksServiceCallback.Stub() { private val serviceCallback = object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { override fun stateChanged(state: Int, profileName: String?, msg: String?) {
val callback = callback ?: return val callback = callback ?: return
handler.post { callback.stateChanged(state, profileName, msg) } handler.post { callback.stateChanged(BaseService.State.values()[state], profileName, msg) }
} }
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
val callback = callback ?: return val callback = callback ?: return
......
...@@ -53,20 +53,22 @@ import java.util.* ...@@ -53,20 +53,22 @@ import java.util.*
* This object uses WeakMap to simulate the effects of multi-inheritance. * This object uses WeakMap to simulate the effects of multi-inheritance.
*/ */
object BaseService { object BaseService {
enum class State(val canStop: Boolean = false) {
/** /**
* IDLE state is only used by UI and will never be returned by BaseService. * Idle state is only used by UI and will never be returned by BaseService.
*/ */
const val IDLE = 0 Idle,
const val CONNECTING = 1 Connecting(true),
const val CONNECTED = 2 Connected(true),
const val STOPPING = 3 Stopping,
const val STOPPED = 4 Stopped,
}
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) {
var state = STOPPED var state = State.Stopped
var processes: GuardedProcessPool? = null var processes: GuardedProcessPool? = null
var proxy: ProxyInstance? = null var proxy: ProxyInstance? = null
var udpFallback: ProxyInstance? = null var udpFallback: ProxyInstance? = null
...@@ -83,7 +85,7 @@ object BaseService { ...@@ -83,7 +85,7 @@ object BaseService {
val binder = Binder(this) val binder = Binder(this)
var connectingJob: Job? = null var connectingJob: Job? = null
fun changeState(s: Int, msg: String? = null) { fun changeState(s: State, msg: String? = null) {
if (state == s && msg == null) return if (state == s && msg == null) return
binder.stateChanged(s, msg) binder.stateChanged(s, msg)
state = s state = s
...@@ -104,7 +106,7 @@ object BaseService { ...@@ -104,7 +106,7 @@ object BaseService {
private val bandwidthListeners = mutableMapOf<IBinder, Long>() // the binder is the real identifier private val bandwidthListeners = mutableMapOf<IBinder, Long>() // the binder is the real identifier
private val handler = Handler() private val handler = Handler()
override fun getState(): Int = data!!.state override fun getState(): Int = data!!.state.ordinal
override fun getProfileName(): String = data!!.proxy?.profile?.name ?: "Idle" override fun getProfileName(): String = data!!.proxy?.profile?.name ?: "Idle"
override fun registerCallback(cb: IShadowsocksServiceCallback) { override fun registerCallback(cb: IShadowsocksServiceCallback) {
...@@ -126,12 +128,12 @@ object BaseService { ...@@ -126,12 +128,12 @@ object BaseService {
handler.postDelayed(this::onTimeout, bandwidthListeners.values.min() ?: return) handler.postDelayed(this::onTimeout, bandwidthListeners.values.min() ?: return)
} }
private fun onTimeout() { private fun onTimeout() {
val proxies = listOfNotNull(data!!.proxy, data!!.udpFallback) val proxies = listOfNotNull(data?.proxy, data?.udpFallback)
val stats = proxies val stats = proxies
.map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) } .map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) }
.filter { it.second != null } .filter { it.second != null }
.map { Triple(it.first, it.second!!.first, it.second!!.second) } .map { Triple(it.first, it.second!!.first, it.second!!.second) }
if (stats.any { it.third } && state == CONNECTED && bandwidthListeners.isNotEmpty()) { if (stats.any { it.third } && data!!.state == State.Connected && bandwidthListeners.isNotEmpty()) {
val sum = stats.fold(TrafficStats()) { a, b -> a + b.second } val sum = stats.fold(TrafficStats()) { a, b -> a + b.second }
broadcast { item -> broadcast { item ->
if (bandwidthListeners.contains(item.asBinder())) { if (bandwidthListeners.contains(item.asBinder())) {
...@@ -147,7 +149,7 @@ object BaseService { ...@@ -147,7 +149,7 @@ object BaseService {
val wasEmpty = bandwidthListeners.isEmpty() val wasEmpty = bandwidthListeners.isEmpty()
if (bandwidthListeners.put(cb.asBinder(), timeout) == null) { if (bandwidthListeners.put(cb.asBinder(), timeout) == null) {
if (wasEmpty) registerTimeout() if (wasEmpty) registerTimeout()
if (state != CONNECTED) return if (data!!.state != State.Connected) return
var sum = TrafficStats() var sum = TrafficStats()
val proxy = data!!.proxy ?: return val proxy = data!!.proxy ?: return
proxy.trafficMonitor?.out.also { stats -> proxy.trafficMonitor?.out.also { stats ->
...@@ -179,9 +181,9 @@ object BaseService { ...@@ -179,9 +181,9 @@ object BaseService {
callbacks.unregister(cb) callbacks.unregister(cb)
} }
fun stateChanged(s: Int, msg: String?) { fun stateChanged(s: State, msg: String?) {
val profileName = profileName val profileName = profileName
broadcast { it.stateChanged(s, profileName, msg) } broadcast { it.stateChanged(s.ordinal, profileName, msg) }
} }
fun trafficPersisted(ids: List<Long>) { fun trafficPersisted(ids: List<Long>) {
...@@ -213,9 +215,9 @@ object BaseService { ...@@ -213,9 +215,9 @@ object BaseService {
return return
} }
val s = data.state val s = data.state
when (s) { when {
STOPPED -> startRunner() s == State.Stopped -> startRunner()
CONNECTED -> stopRunner(true) s.canStop -> stopRunner(true)
else -> Crashlytics.log(Log.WARN, tag, "Illegal state when invoking use: $s") else -> Crashlytics.log(Log.WARN, tag, "Illegal state when invoking use: $s")
} }
} }
...@@ -251,11 +253,12 @@ object BaseService { ...@@ -251,11 +253,12 @@ object BaseService {
} }
fun stopRunner(restart: Boolean = false, msg: String? = null) { fun stopRunner(restart: Boolean = false, msg: String? = null) {
if (data.state == STOPPING) return if (data.state == State.Stopping) return
// channge the state // channge the state
data.changeState(STOPPING) data.changeState(State.Stopping)
GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) { GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) {
Core.analytics.logEvent("stop", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag))) Core.analytics.logEvent("stop", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag)))
data.connectingJob?.cancelAndJoin() // ensure stop connecting first
this@Interface as Service this@Interface as Service
// we use a coroutineScope here to allow clean-up in parallel // we use a coroutineScope here to allow clean-up in parallel
coroutineScope { coroutineScope {
...@@ -280,7 +283,7 @@ object BaseService { ...@@ -280,7 +283,7 @@ object BaseService {
} }
// change the state // change the state
data.changeState(STOPPED, msg) data.changeState(State.Stopped, msg)
// stop the service if nothing has bound to it // stop the service if nothing has bound to it
if (restart) startRunner() else stopSelf() if (restart) startRunner() else stopSelf()
...@@ -293,7 +296,7 @@ object BaseService { ...@@ -293,7 +296,7 @@ object BaseService {
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY if (data.state != State.Stopped) return Service.START_NOT_STICKY
val profilePair = Core.currentProfile val profilePair = Core.currentProfile
this as Context this as Context
if (profilePair == null) { if (profilePair == null) {
...@@ -320,7 +323,7 @@ object BaseService { ...@@ -320,7 +323,7 @@ object BaseService {
data.notification = createNotification(profile.formattedName) data.notification = createNotification(profile.formattedName)
Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag))) Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag)))
data.changeState(CONNECTING) data.changeState(State.Connecting)
data.connectingJob = GlobalScope.launch(Dispatchers.Main) { data.connectingJob = GlobalScope.launch(Dispatchers.Main) {
try { try {
Executable.killAll() // clean up old processes Executable.killAll() // clean up old processes
...@@ -330,7 +333,6 @@ object BaseService { ...@@ -330,7 +333,6 @@ object BaseService {
data.processes = GuardedProcessPool { data.processes = GuardedProcessPool {
printLog(it) printLog(it)
data.connectingJob?.cancelAndJoin()
stopRunner(false, it.readableMessage) stopRunner(false, it.readableMessage)
} }
startProcesses() startProcesses()
...@@ -339,7 +341,7 @@ object BaseService { ...@@ -339,7 +341,7 @@ object BaseService {
data.udpFallback?.scheduleUpdate() data.udpFallback?.scheduleUpdate()
RemoteConfig.fetch() RemoteConfig.fetch()
data.changeState(CONNECTED) data.changeState(State.Connected)
} catch (_: CancellationException) { } catch (_: CancellationException) {
// if the job was cancelled, it is canceller's responsibility to call stopRunner // if the job was cancelled, it is canceller's responsibility to call stopRunner
} catch (_: UnknownHostException) { } catch (_: UnknownHostException) {
......
...@@ -94,7 +94,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -94,7 +94,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
} }
private fun update(action: String?, forceShow: Boolean = false) { private fun update(action: String?, forceShow: Boolean = false) {
if (forceShow || service.data.state == BaseService.CONNECTED) when (action) { if (forceShow || service.data.state == BaseService.State.Connected) when (action) {
Intent.ACTION_SCREEN_OFF -> { Intent.ACTION_SCREEN_OFF -> {
setVisible(false, forceShow) setVisible(false, forceShow)
unregisterCallback() // unregister callback to save battery unregisterCallback() // unregister callback to save battery
......
...@@ -83,8 +83,8 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { ...@@ -83,8 +83,8 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
portTransproxy.isEnabled = enabledTransproxy portTransproxy.isEnabled = enabledTransproxy
true true
} }
val listener: (Int) -> Unit = { val listener: (BaseService.State) -> Unit = {
if (it == BaseService.STOPPED) { if (it == BaseService.State.Stopped) {
tfo.isEnabled = true tfo.isEnabled = true
serviceMode.isEnabled = true serviceMode.isEnabled = true
portProxy.isEnabled = true portProxy.isEnabled = true
......
...@@ -70,7 +70,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -70,7 +70,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
private const val TAG = "ShadowsocksMainActivity" private const val TAG = "ShadowsocksMainActivity"
private const val REQUEST_CONNECT = 1 private const val REQUEST_CONNECT = 1
var stateListener: ((Int) -> Unit)? = null var stateListener: ((BaseService.State) -> Unit)? = null
} }
@Parcelize @Parcelize
...@@ -107,12 +107,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -107,12 +107,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
} }
// service // service
var state = BaseService.IDLE var state = BaseService.State.Idle
override fun stateChanged(state: Int, profileName: String?, msg: String?) = changeState(state, msg, true) override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = changeState(state, msg, true)
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
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.State.Stopping) {
(supportFragmentManager.findFragmentById(R.id.fragment_holder) as? ToolbarFragment) (supportFragmentManager.findFragmentById(R.id.fragment_holder) as? ToolbarFragment)
?.onTrafficUpdated(profileId, stats) ?.onTrafficUpdated(profileId, stats)
} }
...@@ -121,7 +121,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -121,7 +121,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
ProfilesFragment.instance?.onTrafficPersisted(profileId) ProfilesFragment.instance?.onTrafficPersisted(profileId)
} }
private fun changeState(state: Int, msg: String? = null, animate: Boolean = false) { private fun changeState(state: BaseService.State, msg: String? = null, animate: Boolean = false) {
fab.changeState(state, animate) fab.changeState(state, animate)
stats.changeState(state) stats.changeState(state)
if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show() if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show()
...@@ -131,7 +131,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -131,7 +131,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
} }
private fun toggle() = when { private fun toggle() = when {
state == BaseService.CONNECTED -> Core.stopService() state.canStop -> Core.stopService()
DataStore.serviceMode == Key.modeVpn -> { 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)
...@@ -143,11 +143,11 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -143,11 +143,11 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
private val handler = Handler() private val handler = Handler()
private val connection = ShadowsocksConnection(handler, true) private val connection = ShadowsocksConnection(handler, true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try { override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
service.state BaseService.State.values()[service.state]
} catch (_: DeadObjectException) { } catch (_: DeadObjectException) {
BaseService.IDLE BaseService.State.Idle
}) })
override fun onServiceDisconnected() = changeState(BaseService.IDLE) override fun onServiceDisconnected() = changeState(BaseService.State.Idle)
override fun onBinderDied() { override fun onBinderDied() {
connection.disconnect(this) connection.disconnect(this)
connection.connect(this, this) connection.connect(this, this)
...@@ -168,7 +168,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -168,7 +168,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main) setContentView(R.layout.layout_main)
stats = findViewById(R.id.stats) stats = findViewById(R.id.stats)
stats.setOnClickListener { if (state == BaseService.CONNECTED) stats.testConnection() } stats.setOnClickListener { if (state == BaseService.State.Connected) stats.testConnection() }
drawer = findViewById(R.id.drawer) drawer = findViewById(R.id.drawer)
navigation = findViewById(R.id.navigation) navigation = findViewById(R.id.navigation)
navigation.setNavigationItemSelectedListener(this) navigation.setNavigationItemSelectedListener(this)
...@@ -180,7 +180,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -180,7 +180,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
fab = findViewById(R.id.fab) fab = findViewById(R.id.fab)
fab.setOnClickListener { toggle() } fab.setOnClickListener { toggle() }
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.State.Idle) // reset everything to init state
connection.connect(this, this) connection.connect(this, this)
DataStore.publicStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
......
...@@ -72,15 +72,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -72,15 +72,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
/** /**
* Is ProfilesFragment editable at all. * Is ProfilesFragment editable at all.
*/ */
private val isEnabled get() = when ((activity as MainActivity).state) { private val isEnabled get() = (activity as MainActivity).state.let { it.canStop || it == BaseService.State.Stopped }
BaseService.CONNECTED, BaseService.STOPPED -> true private fun isProfileEditable(id: Long) =
else -> false (activity as MainActivity).state == BaseService.State.Stopped || id !in Core.activeProfileIds
}
private fun isProfileEditable(id: Long) = when ((activity as MainActivity).state) {
BaseService.CONNECTED -> id !in Core.activeProfileIds
BaseService.STOPPED -> true
else -> false
}
@SuppressLint("ValidFragment") @SuppressLint("ValidFragment")
class QRCodeDialog() : DialogFragment() { class QRCodeDialog() : DialogFragment() {
...@@ -203,7 +197,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -203,7 +197,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
Core.switchProfile(item.id) Core.switchProfile(item.id)
profilesAdapter.refreshId(old) profilesAdapter.refreshId(old)
itemView.isSelected = true itemView.isSelected = true
if (activity.state == BaseService.CONNECTED) Core.reloadService() if (activity.state.canStop) Core.reloadService()
} }
} }
......
...@@ -55,14 +55,15 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback { ...@@ -55,14 +55,15 @@ class QuickToggleShortcut : Activity(), ShadowsocksConnection.Callback {
} }
override fun onServiceConnected(service: IShadowsocksService) { override fun onServiceConnected(service: IShadowsocksService) {
when (service.state) { val state = BaseService.State.values()[service.state]
BaseService.STOPPED -> Core.startService() when {
BaseService.CONNECTED -> Core.stopService() state.canStop -> Core.stopService()
state == BaseService.State.Stopped -> Core.startService()
} }
finish() finish()
} }
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) { }
override fun onDestroy() { override fun onDestroy() {
connection.disconnect(this) connection.disconnect(this)
......
...@@ -346,11 +346,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -346,11 +346,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
} }
} }
private val isEnabled get() = when ((activity as MainActivity).state) { private val isEnabled get() = (activity as MainActivity).state == BaseService.State.Stopped ||
BaseService.CONNECTED -> Core.currentProfile?.first?.route != Acl.CUSTOM_RULES Core.currentProfile?.first?.route != Acl.CUSTOM_RULES
BaseService.STOPPED -> true
else -> false
}
private val selectedItems = HashSet<Any>() private val selectedItems = HashSet<Any>()
private val adapter by lazy { AclRulesAdapter() } private val adapter by lazy { AclRulesAdapter() }
......
...@@ -41,9 +41,9 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -41,9 +41,9 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
private var tapPending = false private var tapPending = false
private val connection = ShadowsocksConnection() private val connection = ShadowsocksConnection()
override fun stateChanged(state: Int, profileName: String?, msg: String?) = updateTile(state) { profileName } override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = updateTile(state) { profileName }
override fun onServiceConnected(service: IShadowsocksService) { override fun onServiceConnected(service: IShadowsocksService) {
updateTile(service.state) { service.profileName } updateTile(BaseService.State.values()[service.state]) { service.profileName }
if (tapPending) { if (tapPending) {
tapPending = false tapPending = false
onClick() onClick()
...@@ -63,23 +63,28 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -63,23 +63,28 @@ 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?) { private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) {
qsTile?.apply { qsTile?.apply {
label = null label = null
when (serviceState) { when (serviceState) {
BaseService.STOPPED -> { BaseService.State.Idle -> throw IllegalStateException("serviceState")
icon = iconIdle BaseService.State.Connecting -> {
state = Tile.STATE_INACTIVE icon = iconBusy
state = Tile.STATE_ACTIVE
} }
BaseService.CONNECTED -> { BaseService.State.Connected -> {
icon = iconConnected icon = iconConnected
if (!keyguard.isDeviceLocked) label = profileName() if (!keyguard.isDeviceLocked) label = profileName()
state = Tile.STATE_ACTIVE state = Tile.STATE_ACTIVE
} }
else -> { BaseService.State.Stopping -> {
icon = iconBusy icon = iconBusy
state = Tile.STATE_UNAVAILABLE state = Tile.STATE_UNAVAILABLE
} }
BaseService.State.Stopped -> {
icon = iconIdle
state = Tile.STATE_INACTIVE
}
} }
label = label ?: getString(R.string.app_name) label = label ?: getString(R.string.app_name)
updateTile() updateTile()
...@@ -88,9 +93,11 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback { ...@@ -88,9 +93,11 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
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 BaseService.State.values()[service.state].let { state ->
BaseService.STOPPED -> Core.startService() when {
BaseService.CONNECTED -> Core.stopService() state.canStop -> Core.stopService()
state == BaseService.State.Stopped -> Core.startService()
}
} }
} }
} }
...@@ -68,14 +68,14 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute ...@@ -68,14 +68,14 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
return drawableState return drawableState
} }
fun changeState(state: Int, animate: Boolean) { fun changeState(state: BaseService.State, animate: Boolean) {
when (state) { when (state) {
BaseService.CONNECTING -> changeState(iconConnecting, animate) BaseService.State.Connecting -> changeState(iconConnecting, animate)
BaseService.CONNECTED -> changeState(iconConnected, animate) BaseService.State.Connected -> changeState(iconConnected, animate)
BaseService.STOPPING -> changeState(iconStopping, animate) BaseService.State.Stopping -> changeState(iconStopping, animate)
else -> changeState(iconStopped, animate) else -> changeState(iconStopped, animate)
} }
if (state == BaseService.CONNECTED) { if (state == BaseService.State.Connected) {
checked = true checked = true
TooltipCompat.setTooltipText(this, context.getString(R.string.stop)) TooltipCompat.setTooltipText(this, context.getString(R.string.stop))
} else { } else {
...@@ -83,7 +83,7 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute ...@@ -83,7 +83,7 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
TooltipCompat.setTooltipText(this, context.getString(R.string.connect)) TooltipCompat.setTooltipText(this, context.getString(R.string.connect))
} }
refreshDrawableState() refreshDrawableState()
isEnabled = state == BaseService.CONNECTED || state == BaseService.STOPPED isEnabled = state.canStop || state == BaseService.State.Stopped
} }
private fun counters(a: AnimatedVectorDrawableCompat, b: AnimatedVectorDrawableCompat): Boolean = private fun counters(a: AnimatedVectorDrawableCompat, b: AnimatedVectorDrawableCompat): Boolean =
......
...@@ -65,15 +65,15 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? ...@@ -65,15 +65,15 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet?
super.setOnClickListener(l) super.setOnClickListener(l)
} }
fun changeState(state: Int) { fun changeState(state: BaseService.State) {
val activity = context as MainActivity val activity = context as MainActivity
if (state != BaseService.CONNECTED) { if (state != BaseService.State.Connected) {
updateTraffic(0, 0, 0, 0) updateTraffic(0, 0, 0, 0)
tester.status.removeObservers(activity) tester.status.removeObservers(activity)
if (state != BaseService.IDLE) tester.invalidate() if (state != BaseService.State.Idle) tester.invalidate()
statusText.setText(when (state) { statusText.setText(when (state) {
BaseService.CONNECTING -> R.string.connecting BaseService.State.Connecting -> R.string.connecting
BaseService.STOPPING -> R.string.stopping BaseService.State.Stopping -> R.string.stopping
else -> R.string.not_connected else -> R.string.not_connected
}) })
} else { } else {
......
...@@ -42,7 +42,7 @@ class MainFragment : LeanbackSettingsFragmentCompat() { ...@@ -42,7 +42,7 @@ class MainFragment : LeanbackSettingsFragmentCompat() {
override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, pref: Preference?): Boolean { override fun onPreferenceDisplayDialog(caller: PreferenceFragmentCompat, pref: Preference?): Boolean {
if (pref?.key == Key.id) { if (pref?.key == Key.id) {
if ((childFragmentManager.findFragmentById(R.id.settings_preference_fragment_container) if ((childFragmentManager.findFragmentById(R.id.settings_preference_fragment_container)
as MainPreferenceFragment).state == BaseService.STOPPED) { as MainPreferenceFragment).state == BaseService.State.Stopped) {
startPreferenceFragment(ProfilesDialogFragment().apply { startPreferenceFragment(ProfilesDialogFragment().apply {
arguments = bundleOf(Pair(LeanbackPreferenceDialogFragmentCompat.ARG_KEY, Key.id)) arguments = bundleOf(Pair(LeanbackPreferenceDialogFragmentCompat.ARG_KEY, Key.id))
setTargetFragment(caller, 0) setTargetFragment(caller, 0)
......
...@@ -88,9 +88,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -88,9 +88,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private lateinit var tester: HttpsTest private lateinit var tester: HttpsTest
// service // service
var state = BaseService.IDLE var state = BaseService.State.Idle
private set private set
override fun stateChanged(state: Int, profileName: String?, msg: String?) = changeState(state, msg) override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = changeState(state, msg)
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
if (profileId == 0L) requireContext().let { context -> if (profileId == 0L) requireContext().let { context ->
this.stats.summary = getString(R.string.stat_summary, this.stats.summary = getString(R.string.stat_summary,
...@@ -101,26 +101,26 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -101,26 +101,26 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
} }
} }
private fun changeState(state: Int, msg: String? = null) { private fun changeState(state: BaseService.State, msg: String? = null) {
fab.isEnabled = state == BaseService.STOPPED || state == BaseService.CONNECTED fab.isEnabled = state.canStop || state == BaseService.State.Stopped
fab.setTitle(when (state) { fab.setTitle(when (state) {
BaseService.CONNECTING -> R.string.connecting BaseService.State.Connecting -> R.string.connecting
BaseService.CONNECTED -> R.string.stop BaseService.State.Connected -> R.string.stop
BaseService.STOPPING -> R.string.stopping BaseService.State.Stopping -> R.string.stopping
else -> R.string.connect else -> R.string.connect
}) })
stats.setTitle(R.string.connection_test_pending) stats.setTitle(R.string.connection_test_pending)
stats.isVisible = state == BaseService.CONNECTED stats.isVisible = state == BaseService.State.Connected
if (state != BaseService.CONNECTED) { if (state != BaseService.State.Connected) {
trafficUpdated(0, TrafficStats()) trafficUpdated(0, TrafficStats())
tester.status.removeObservers(this) tester.status.removeObservers(this)
if (state != BaseService.IDLE) tester.invalidate() if (state != BaseService.State.Idle) tester.invalidate()
} else tester.status.observe(this, Observer { } else tester.status.observe(this, Observer {
it.retrieve(stats::setTitle) { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() } it.retrieve(stats::setTitle) { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
}) })
if (msg != null) Toast.makeText(requireContext(), getString(R.string.vpn_error, msg), Toast.LENGTH_SHORT).show() if (msg != null) Toast.makeText(requireContext(), getString(R.string.vpn_error, msg), Toast.LENGTH_SHORT).show()
this.state = state this.state = state
if (state == BaseService.STOPPED) { if (state == BaseService.State.Stopped) {
controlImport.isEnabled = true controlImport.isEnabled = true
tfo.isEnabled = true tfo.isEnabled = true
serviceMode.isEnabled = true serviceMode.isEnabled = true
...@@ -141,11 +141,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -141,11 +141,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private val handler = Handler() private val handler = Handler()
private val connection = ShadowsocksConnection(handler, true) private val connection = ShadowsocksConnection(handler, true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try { override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
service.state BaseService.State.values()[service.state]
} catch (_: DeadObjectException) { } catch (_: DeadObjectException) {
BaseService.IDLE BaseService.State.Idle
}) })
override fun onServiceDisconnected() = changeState(BaseService.IDLE) override fun onServiceDisconnected() = changeState(BaseService.State.Idle)
override fun onBinderDied() { override fun onBinderDied() {
connection.disconnect(requireContext()) connection.disconnect(requireContext())
connection.connect(requireContext(), this) connection.connect(requireContext(), this)
...@@ -200,7 +200,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -200,7 +200,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
} }
tester = ViewModelProviders.of(this).get() tester = ViewModelProviders.of(this).get()
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.State.Idle) // reset everything to init state
connection.connect(requireContext(), this) connection.connect(requireContext(), this)
DataStore.publicStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
} }
...@@ -225,7 +225,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -225,7 +225,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
fun startService() { fun startService() {
when { when {
state != BaseService.STOPPED -> return state != BaseService.State.Stopped -> return
DataStore.serviceMode == Key.modeVpn -> { DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(requireContext()) val intent = VpnService.prepare(requireContext())
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
...@@ -251,7 +251,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -251,7 +251,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
override fun onPreferenceTreeClick(preference: Preference?) = when (preference?.key) { override fun onPreferenceTreeClick(preference: Preference?) = when (preference?.key) {
Key.id -> { Key.id -> {
if (state == BaseService.CONNECTED) Core.stopService() if (state == BaseService.State.Connected) Core.stopService()
true true
} }
Key.controlStats -> { Key.controlStats -> {
......
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