summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2021-01-05 13:36:07 +0000
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2021-04-12 13:19:37 +0000
commit5a4c19dfddd66169d230aa1be9f59b2a2c4d9310 (patch)
tree8facf2e23f014a8ad428f1b434eae997c8a257cb
parentbf57a7a1b4b31a0be7192401b38eeab542e68162 (diff)
downloadmullvadvpn-5a4c19dfddd66169d230aa1be9f59b2a2c4d9310.tar.xz
mullvadvpn-5a4c19dfddd66169d230aa1be9f59b2a2c4d9310.zip
Handle UI tunnel state predictions in UI-side
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt113
1 files changed, 109 insertions, 4 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
index c34983ae03..35714d1ce5 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt
@@ -1,34 +1,139 @@
package net.mullvad.mullvadvpn.ui.serviceconnection
import android.os.Messenger
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.ipc.DispatchingHandler
import net.mullvad.mullvadvpn.ipc.Event
import net.mullvad.mullvadvpn.ipc.Request
import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.util.EventNotifier
+val ANTICIPATED_STATE_TIMEOUT_MS = 1500L
+
class ConnectionProxy(val connection: Messenger, eventDispatcher: DispatchingHandler<Event>) {
+ private var resetAnticipatedStateJob: Job? = null
+
val onStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected)
+ val onUiStateChange = EventNotifier<TunnelState>(TunnelState.Disconnected)
+
+ var state by onStateChange.notifiable()
+ private set
+ var uiState by onUiStateChange.notifiable()
+ private set
init {
eventDispatcher.registerHandler(Event.TunnelStateChange::class) { event ->
- onStateChange.notify(event.tunnelState)
+ handleNewState(event.tunnelState)
}
}
fun connect() {
- connection.send(Request.Connect.message)
+ if (anticipateConnectingState()) {
+ connection.send(Request.Connect.message)
+ }
}
fun disconnect() {
- connection.send(Request.Disconnect.message)
+ if (anticipateReconnectingState()) {
+ connection.send(Request.Disconnect.message)
+ }
}
fun reconnect() {
- connection.send(Request.Reconnect.message)
+ if (anticipateDisconnectingState()) {
+ connection.send(Request.Reconnect.message)
+ }
}
fun onDestroy() {
onStateChange.unsubscribeAll()
+ onUiStateChange.unsubscribeAll()
+ }
+
+ private fun handleNewState(newState: TunnelState) {
+ synchronized(this) {
+ resetAnticipatedStateJob?.cancel()
+ state = newState
+ uiState = newState
+ }
+ }
+
+ private fun anticipateConnectingState(): Boolean {
+ synchronized(this) {
+ val currentState = uiState
+
+ if (currentState is TunnelState.Connecting || currentState is TunnelState.Connected) {
+ return false
+ } else {
+ scheduleToResetAnticipatedState()
+ uiState = TunnelState.Connecting(null, null)
+ return true
+ }
+ }
+ }
+
+ private fun anticipateReconnectingState(): Boolean {
+ synchronized(this) {
+ val currentState = uiState
+
+ val willReconnect = when (currentState) {
+ is TunnelState.Disconnected -> false
+ is TunnelState.Disconnecting -> {
+ when (currentState.actionAfterDisconnect) {
+ ActionAfterDisconnect.Nothing -> false
+ ActionAfterDisconnect.Reconnect -> true
+ ActionAfterDisconnect.Block -> true
+ }
+ }
+ is TunnelState.Connecting -> true
+ is TunnelState.Connected -> true
+ is TunnelState.Error -> true
+ }
+
+ if (willReconnect) {
+ scheduleToResetAnticipatedState()
+ uiState = TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect)
+ }
+
+ return willReconnect
+ }
+ }
+
+ private fun anticipateDisconnectingState(): Boolean {
+ synchronized(this) {
+ val currentState = uiState
+
+ if (currentState is TunnelState.Disconnected) {
+ return false
+ } else {
+ scheduleToResetAnticipatedState()
+ uiState = TunnelState.Disconnecting(ActionAfterDisconnect.Nothing)
+ return true
+ }
+ }
+ }
+
+ private fun scheduleToResetAnticipatedState() {
+ resetAnticipatedStateJob?.cancel()
+
+ var currentJob: Job? = null
+
+ val newJob = GlobalScope.launch(Dispatchers.Default) {
+ delay(ANTICIPATED_STATE_TIMEOUT_MS)
+
+ synchronized(this@ConnectionProxy) {
+ if (!currentJob!!.isCancelled) {
+ uiState = state
+ }
+ }
+ }
+
+ currentJob = newJob
+ resetAnticipatedStateJob = newJob
}
}