diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-03-18 10:58:26 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-03-18 10:58:26 -0300 |
| commit | b4d7858e0d16f23b1d32029924e20aa863a26b80 (patch) | |
| tree | 01b0ae8153bdb426250e0015cfd6ad1cc0c2e1e2 /android/src | |
| parent | b94272f83c2af3ec0657ced3758949dcb7c16081 (diff) | |
| parent | 6fedeae09c0620b6b4277a255373c3c16f053501 (diff) | |
| download | mullvadvpn-b4d7858e0d16f23b1d32029924e20aa863a26b80.tar.xz mullvadvpn-b4d7858e0d16f23b1d32029924e20aa863a26b80.zip | |
Merge branch 'quick-tile'
Diffstat (limited to 'android/src')
18 files changed, 271 insertions, 1 deletions
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 8f472d7f5b..cbd48f152e 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -27,5 +27,14 @@ <action android:name="android.net.VpnService" /> </intent-filter> </service> + <service android:name="net.mullvad.mullvadvpn.service.MullvadTileService" + android:label="@string/app_name" + android:icon="@drawable/small_logo_black" + android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> + + <intent-filter> + <action android:name="android.service.quicksettings.action.QS_TILE" /> + </intent-filter> + </service> </application> </manifest> diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt index 556517720d..e4b67e2f1b 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.model import net.mullvad.talpid.net.TunnelEndpoint import net.mullvad.talpid.tunnel.ActionAfterDisconnect import net.mullvad.talpid.tunnel.ErrorState +import net.mullvad.talpid.tunnel.ErrorStateCause sealed class TunnelState() { class Disconnected() : TunnelState() @@ -10,4 +11,51 @@ sealed class TunnelState() { class Connected(val endpoint: TunnelEndpoint, val location: GeoIpLocation?) : TunnelState() class Disconnecting(val actionAfterDisconnect: ActionAfterDisconnect) : TunnelState() class Error(val errorState: ErrorState) : TunnelState() + + companion object { + const val DISCONNECTED = "disconnected" + const val CONNECTING = "connecting" + const val CONNECTED = "connected" + const val RECONNECTING = "reconnecting" + const val DISCONNECTING = "disconnecting" + const val BLOCKING = "blocking" + const val ERROR = "error" + + fun fromString(description: String, endpoint: TunnelEndpoint?): TunnelState { + return when (description) { + DISCONNECTED -> TunnelState.Disconnected() + CONNECTING -> TunnelState.Connecting(endpoint, null) + CONNECTED -> TunnelState.Connected(endpoint!!, null) + RECONNECTING -> TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect) + DISCONNECTING -> TunnelState.Disconnecting(ActionAfterDisconnect.Nothing) + BLOCKING -> TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError(), true)) + ERROR -> { + TunnelState.Error(ErrorState(ErrorStateCause.SetFirewallPolicyError(), false)) + } + else -> { + TunnelState.Error(ErrorState(ErrorStateCause.SetFirewallPolicyError(), false)) + } + } + } + } + + override fun toString() = when (this) { + is TunnelState.Disconnected -> DISCONNECTED + is TunnelState.Connecting -> CONNECTING + is TunnelState.Connected -> CONNECTED + is TunnelState.Disconnecting -> { + if (actionAfterDisconnect == ActionAfterDisconnect.Reconnect) { + RECONNECTING + } else { + DISCONNECTING + } + } + is TunnelState.Error -> { + if (errorState.isBlocking) { + BLOCKING + } else { + ERROR + } + } + } } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt index 733a885b09..aabd9771a3 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt @@ -252,7 +252,7 @@ class ForegroundNotificationManager( PendingIntent.getActivity(service, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT) val builder = NotificationCompat.Builder(service, CHANNEL_ID) - .setSmallIcon(R.drawable.notification) + .setSmallIcon(R.drawable.small_logo_black) .setColor(badgeColor) .setContentTitle(service.getString(notificationText)) .setContentIntent(pendingIntent) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt new file mode 100644 index 0000000000..e8eb8a3a82 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt @@ -0,0 +1,86 @@ +package net.mullvad.mullvadvpn.service + +import android.content.Intent +import android.graphics.drawable.Icon +import android.service.quicksettings.Tile +import android.service.quicksettings.TileService +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.mullvadvpn.service.tunnelstate.TunnelStateListener +import net.mullvad.talpid.tunnel.ActionAfterDisconnect + +class MullvadTileService : TileService() { + private var secured = false + set(value) { + if (field != value) { + field = value + updateTileState() + } + } + + private lateinit var listener: TunnelStateListener + private lateinit var securedIcon: Icon + private lateinit var unsecuredIcon: Icon + + override fun onCreate() { + super.onCreate() + + listener = TunnelStateListener(this) + securedIcon = Icon.createWithResource(this, R.drawable.small_logo_white) + unsecuredIcon = Icon.createWithResource(this, R.drawable.small_logo_black) + } + + override fun onStartListening() { + super.onStartListening() + + listener.onStateChange = { state -> + secured = when (state) { + is TunnelState.Disconnected -> false + is TunnelState.Connecting -> true + is TunnelState.Connected -> true + is TunnelState.Disconnecting -> { + state.actionAfterDisconnect == ActionAfterDisconnect.Reconnect + } + is TunnelState.Error -> { + state.errorState.isBlocking + } + } + } + + updateTileState() + } + + override fun onClick() { + super.onClick() + + val tunnelActionKey = if (secured) { + KEY_DISCONNECT_ACTION + } else { + KEY_CONNECT_ACTION + } + + val intent = Intent(tunnelActionKey).setPackage("net.mullvad.mullvadvpn") + + sendBroadcast(intent) + } + + override fun onStopListening() { + super.onStartListening() + + listener.onStateChange = null + } + + private fun updateTileState() { + qsTile.apply { + if (secured) { + state = Tile.STATE_ACTIVE + icon = securedIcon + } else { + state = Tile.STATE_INACTIVE + icon = unsecuredIcon + } + + updateTile() + } + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index cdfdbac287..49a58662d1 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy +import net.mullvad.mullvadvpn.service.tunnelstate.TunnelStateUpdater import net.mullvad.talpid.TalpidVpnService import net.mullvad.talpid.util.EventNotifier @@ -29,6 +30,7 @@ class MullvadVpnService : TalpidVpnService() { private var startDaemonJob: Job? = null private lateinit var notificationManager: ForegroundNotificationManager + private lateinit var tunnelStateUpdater: TunnelStateUpdater var shouldConnect = false set(value) { @@ -56,7 +58,10 @@ class MullvadVpnService : TalpidVpnService() { override fun onCreate() { super.onCreate() + notificationManager = ForegroundNotificationManager(this, serviceNotifier) + tunnelStateUpdater = TunnelStateUpdater(this, serviceNotifier) + setUp() } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/Persistence.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/Persistence.kt new file mode 100644 index 0000000000..f1366abc66 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/Persistence.kt @@ -0,0 +1,57 @@ +package net.mullvad.mullvadvpn.service.tunnelstate + +import android.content.Context +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import java.net.InetSocketAddress +import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.talpid.net.Endpoint +import net.mullvad.talpid.net.TransportProtocol +import net.mullvad.talpid.net.TunnelEndpoint + +private const val SHARED_PREFERENCES = "tunnel_state" +private const val KEY_TUNNEL_STATE = "tunnel_state" + +// TODO: Maybe replace using this with actually persisting the endpoint information +private val dummyTunnelEndpoint = TunnelEndpoint(Endpoint( + InetSocketAddress.createUnresolved("dummy", 53), + TransportProtocol.Tcp +)) + +internal class Persistence(context: Context) { + val sharedPreferences = + context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE) + + var state + get() = loadState() + set(value) { + persistState(value) + } + + var listener: OnSharedPreferenceChangeListener? = null + set(value) { + if (value != field) { + if (field != null) { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(field) + } + + if (value != null) { + sharedPreferences.registerOnSharedPreferenceChangeListener(value) + } + + field = value + } + } + + private fun loadState(): TunnelState { + val description = sharedPreferences.getString(KEY_TUNNEL_STATE, TunnelState.DISCONNECTED)!! + + return TunnelState.fromString(description, dummyTunnelEndpoint) + } + + private fun persistState(state: TunnelState) { + sharedPreferences + .edit() + .putString(KEY_TUNNEL_STATE, state.toString()) + .commit() + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/TunnelStateListener.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/TunnelStateListener.kt new file mode 100644 index 0000000000..7f7832d3e4 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/TunnelStateListener.kt @@ -0,0 +1,36 @@ +package net.mullvad.mullvadvpn.service.tunnelstate + +import android.content.Context +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import net.mullvad.mullvadvpn.model.TunnelState + +class TunnelStateListener(context: Context) { + private val persistence = Persistence(context) + + private val listener = object : OnSharedPreferenceChangeListener { + override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) { + state = persistence.state + } + } + + var state = persistence.state + private set(value) { + if (field != value) { + field = value + onStateChange?.invoke(value) + } + } + + var onStateChange: ((TunnelState) -> Unit)? = null + set(value) { + field = value + + if (value == null) { + persistence.listener = null + } else { + persistence.listener = listener + state = persistence.state + } + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/TunnelStateUpdater.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/TunnelStateUpdater.kt new file mode 100644 index 0000000000..133c81506e --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/tunnelstate/TunnelStateUpdater.kt @@ -0,0 +1,29 @@ +package net.mullvad.mullvadvpn.service.tunnelstate + +import android.content.Context +import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy +import net.mullvad.mullvadvpn.service.ServiceInstance +import net.mullvad.talpid.util.EventNotifier + +class TunnelStateUpdater(context: Context, serviceNotifier: EventNotifier<ServiceInstance?>) { + private val persistence = Persistence(context) + + private var connectionProxy: ConnectionProxy? = null + private var stateSubscriptionId: Int? = null + + init { + serviceNotifier.subscribe { serviceInstance -> + onNewServiceInstance(serviceInstance) + } + } + + private fun onNewServiceInstance(serviceInstance: ServiceInstance?) { + stateSubscriptionId?.let { id -> connectionProxy?.onStateChange?.unsubscribe(id) } + + connectionProxy = serviceInstance?.connectionProxy?.apply { + stateSubscriptionId = onStateChange.subscribe { newState -> + persistence.state = newState + } + } + } +} diff --git a/android/src/main/res/drawable-hdpi/notification.png b/android/src/main/res/drawable-hdpi/small_logo_black.png Binary files differindex a6aa370201..a6aa370201 100644 --- a/android/src/main/res/drawable-hdpi/notification.png +++ b/android/src/main/res/drawable-hdpi/small_logo_black.png diff --git a/android/src/main/res/drawable-hdpi/small_logo_white.png b/android/src/main/res/drawable-hdpi/small_logo_white.png Binary files differnew file mode 100644 index 0000000000..e590941f60 --- /dev/null +++ b/android/src/main/res/drawable-hdpi/small_logo_white.png diff --git a/android/src/main/res/drawable-mdpi/notification.png b/android/src/main/res/drawable-mdpi/small_logo_black.png Binary files differindex d1a7dc4d5f..d1a7dc4d5f 100644 --- a/android/src/main/res/drawable-mdpi/notification.png +++ b/android/src/main/res/drawable-mdpi/small_logo_black.png diff --git a/android/src/main/res/drawable-mdpi/small_logo_white.png b/android/src/main/res/drawable-mdpi/small_logo_white.png Binary files differnew file mode 100644 index 0000000000..792c7746e8 --- /dev/null +++ b/android/src/main/res/drawable-mdpi/small_logo_white.png diff --git a/android/src/main/res/drawable-xhdpi/notification.png b/android/src/main/res/drawable-xhdpi/small_logo_black.png Binary files differindex 7ce258b490..7ce258b490 100644 --- a/android/src/main/res/drawable-xhdpi/notification.png +++ b/android/src/main/res/drawable-xhdpi/small_logo_black.png diff --git a/android/src/main/res/drawable-xhdpi/small_logo_white.png b/android/src/main/res/drawable-xhdpi/small_logo_white.png Binary files differnew file mode 100644 index 0000000000..21f3f411b3 --- /dev/null +++ b/android/src/main/res/drawable-xhdpi/small_logo_white.png diff --git a/android/src/main/res/drawable-xxhdpi/notification.png b/android/src/main/res/drawable-xxhdpi/small_logo_black.png Binary files differindex edebf879d3..edebf879d3 100644 --- a/android/src/main/res/drawable-xxhdpi/notification.png +++ b/android/src/main/res/drawable-xxhdpi/small_logo_black.png diff --git a/android/src/main/res/drawable-xxhdpi/small_logo_white.png b/android/src/main/res/drawable-xxhdpi/small_logo_white.png Binary files differnew file mode 100644 index 0000000000..0cfb2f889e --- /dev/null +++ b/android/src/main/res/drawable-xxhdpi/small_logo_white.png diff --git a/android/src/main/res/drawable-xxxhdpi/notification.png b/android/src/main/res/drawable-xxxhdpi/small_logo_black.png Binary files differindex b2b88998cb..b2b88998cb 100644 --- a/android/src/main/res/drawable-xxxhdpi/notification.png +++ b/android/src/main/res/drawable-xxxhdpi/small_logo_black.png diff --git a/android/src/main/res/drawable-xxxhdpi/small_logo_white.png b/android/src/main/res/drawable-xxxhdpi/small_logo_white.png Binary files differnew file mode 100644 index 0000000000..18b6f286f0 --- /dev/null +++ b/android/src/main/res/drawable-xxxhdpi/small_logo_white.png |
