diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-08-29 14:08:59 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-08-29 14:08:59 -0300 |
| commit | cab99e295609afa3c2933194640d2d98ac7b7ffa (patch) | |
| tree | 0e4d975dadc1b32e9b0f74952807d43db57df999 /android | |
| parent | 454e6893985b010a8ca2c07d288d44995348bd48 (diff) | |
| parent | 42b6948f2b3bf675c2172ddb04ba12781b944421 (diff) | |
| download | mullvadvpn-cab99e295609afa3c2933194640d2d98ac7b7ffa.tar.xz mullvadvpn-cab99e295609afa3c2933194640d2d98ac7b7ffa.zip | |
Merge branch 'foreground-notification'
Diffstat (limited to 'android')
18 files changed, 367 insertions, 83 deletions
diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index a50d295303..30ff963a19 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -1,4 +1,5 @@ -dontwarn org.joda.convert.** +-dontwarn net.mullvad.mullvadvpn.ConnectFragment$onCreateView$2$? -keep class net.mullvad.mullvadvpn.model.** { *; } -keep class net.mullvad.mullvadvpn.MullvadDaemon { *; } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 9dbdab3612..5cacbd46b2 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ package="net.mullvad.mullvadvpn" > <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.INTERNET" /> <application diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt index 1e7f16e94d..7e74c53066 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt @@ -18,6 +18,7 @@ import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache import net.mullvad.mullvadvpn.dataproxy.RelayListListener +import net.mullvad.mullvadvpn.util.SmartDeferred import net.mullvad.mullvadvpn.model.KeygenEvent import net.mullvad.mullvadvpn.model.TunnelState @@ -32,16 +33,18 @@ class ConnectFragment : Fragment() { private lateinit var locationInfo: LocationInfo private lateinit var parentActivity: MainActivity - private lateinit var connectionProxy: ConnectionProxy + private lateinit var connectionProxy: SmartDeferred<ConnectionProxy> private lateinit var keyStatusListener: KeyStatusListener private lateinit var locationInfoCache: LocationInfoCache private lateinit var relayListListener: RelayListListener private lateinit var versionInfoCache: AppVersionInfoCache private lateinit var updateKeyStatusJob: Job - private lateinit var updateTunnelStateJob: Job + private var updateTunnelStateJob: Job? = null + private var tunnelStateSubscriptionJob: Long? = null private var isTunnelInfoExpanded = false + private var tunnelStateListener: Int? = null override fun onAttach(context: Context) { super.onAttach(context) @@ -81,9 +84,9 @@ class ConnectFragment : Fragment() { actionButton = ConnectActionButton(view) actionButton.apply { - onConnect = { connectionProxy.connect() } - onCancel = { connectionProxy.disconnect() } - onDisconnect = { connectionProxy.disconnect() } + onConnect = { connectionProxy.awaitThen { connect() } } + onCancel = { connectionProxy.awaitThen { disconnect() } } + onDisconnect = { connectionProxy.awaitThen { disconnect() } } } switchLocationButton = SwitchLocationButton(view, resources) @@ -97,10 +100,10 @@ class ConnectFragment : Fragment() { override fun onResume() { super.onResume() - notificationBanner.onResume() - locationInfo.isTunnelInfoExpanded = isTunnelInfoExpanded + notificationBanner.onResume() + keyStatusListener.onKeyStatusChange = { keyStatus -> updateKeyStatusJob.cancel() updateKeyStatusJob = updateKeyStatus(keyStatus) @@ -114,10 +117,11 @@ class ConnectFragment : Fragment() { switchLocationButton.location = selectedRelayItem } - updateTunnelStateJob = updateTunnelState(connectionProxy.uiState) - connectionProxy.onUiStateChange = { uiState -> - updateTunnelStateJob.cancel() - updateTunnelStateJob = updateTunnelState(uiState) + tunnelStateSubscriptionJob = connectionProxy.awaitThen { + tunnelStateListener = onUiStateChange.subscribe { uiState -> + updateTunnelStateJob?.cancel() + updateTunnelStateJob = updateTunnelState(uiState, state) + } } } @@ -126,14 +130,21 @@ class ConnectFragment : Fragment() { locationInfoCache.onNewLocation = null relayListListener.onRelayListChange = null - connectionProxy.onUiStateChange = null - updateTunnelStateJob.cancel() - + tunnelStateSubscriptionJob?.let { jobId -> + connectionProxy.cancelJob(jobId) + } - isTunnelInfoExpanded = locationInfo.isTunnelInfoExpanded + tunnelStateListener?.let { listener -> + connectionProxy.awaitThen { + onUiStateChange.unsubscribe(listener) + } + } + updateTunnelStateJob?.cancel() notificationBanner.onPause() + isTunnelInfoExpanded = locationInfo.isTunnelInfoExpanded + super.onPause() } @@ -148,9 +159,9 @@ class ConnectFragment : Fragment() { state.putBoolean(KEY_IS_TUNNEL_INFO_EXPANDED, isTunnelInfoExpanded) } - private fun updateTunnelState(uiState: TunnelState) = GlobalScope.launch(Dispatchers.Main) { - val realState = connectionProxy.state - + private fun updateTunnelState(uiState: TunnelState, realState: TunnelState) = + GlobalScope.launch(Dispatchers.Main) + { locationInfoCache.state = realState locationInfo.state = realState headerBar.setState(realState) diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ForegroundNotificationManager.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ForegroundNotificationManager.kt new file mode 100644 index 0000000000..5718e66036 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ForegroundNotificationManager.kt @@ -0,0 +1,100 @@ +package net.mullvad.mullvadvpn + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.support.v4.app.NotificationCompat + +import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy +import net.mullvad.mullvadvpn.model.ActionAfterDisconnect +import net.mullvad.mullvadvpn.model.TunnelState + +val FOREGROUND_NOTIFICATION_ID: Int = 1 + +class ForegroundNotificationManager(val service: Service, val connectionProxy: ConnectionProxy) { + private var listenerId: Int? = null + private var reconnecting = false + private var showingReconnecting = false + + private lateinit var notificationManager: NotificationManager + + private var tunnelState: TunnelState = TunnelState.Disconnected() + set(value) { + field = value + + reconnecting = + (value is TunnelState.Disconnecting + && value.actionAfterDisconnect is ActionAfterDisconnect.Reconnect) + || (value is TunnelState.Connecting && reconnecting) + + updateNotification() + } + + private val notificationText: Int + get() { + val state = tunnelState + + return when (state) { + is TunnelState.Disconnected -> R.string.unsecured + is TunnelState.Connecting -> { + if (reconnecting) { + R.string.reconnecting + } else { + R.string.connecting + } + } + is TunnelState.Connected -> R.string.secured + is TunnelState.Disconnecting -> { + when (state.actionAfterDisconnect) { + is ActionAfterDisconnect.Reconnect -> R.string.reconnecting + else -> R.string.disconnecting + } + } + is TunnelState.Blocked -> R.string.blocking_all_connections + } + } + + fun onCreate() { + notificationManager = + service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + listenerId = connectionProxy.onUiStateChange.subscribe { uiState -> + tunnelState = uiState + } + + service.startForeground(FOREGROUND_NOTIFICATION_ID, buildNotification()) + } + + fun onDestroy() { + listenerId?.let { listener -> + connectionProxy.onUiStateChange.unsubscribe(listener) + } + + service.stopForeground(FOREGROUND_NOTIFICATION_ID) + } + + private fun updateNotification() { + if (!reconnecting || !showingReconnecting) { + notificationManager.notify(FOREGROUND_NOTIFICATION_ID, buildNotification()) + } + } + + private fun buildNotification(): Notification { + val intent = Intent(service, MainActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + .setAction(Intent.ACTION_MAIN) + + val pendingIntent = + PendingIntent.getActivity(service, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT) + + return NotificationCompat.Builder(service) + .setSmallIcon(R.drawable.notification) + .setColor(service.getColor(R.color.colorPrimary)) + .setContentTitle(service.getString(notificationText)) + .setContentIntent(pendingIntent) + .build() + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt index ffc11354ff..fff27be8b5 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt @@ -13,14 +13,12 @@ import android.app.Activity import android.content.ComponentName import android.content.Intent import android.content.ServiceConnection -import android.net.VpnService import android.os.Bundle import android.os.IBinder import android.support.v4.app.FragmentActivity import net.mullvad.mullvadvpn.dataproxy.AccountCache import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache -import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport @@ -30,9 +28,12 @@ import net.mullvad.mullvadvpn.model.RelaySettings import net.mullvad.mullvadvpn.model.Settings import net.mullvad.mullvadvpn.relaylist.RelayItem import net.mullvad.mullvadvpn.relaylist.RelayList +import net.mullvad.mullvadvpn.util.SmartDeferred class MainActivity : FragmentActivity() { - private var vpnPermission: CompletableDeferred<Boolean>? = null + companion object { + val KEY_SHOULD_CONNECT = "should_connect" + } var daemon = CompletableDeferred<MullvadDaemon>() private set @@ -40,7 +41,7 @@ class MainActivity : FragmentActivity() { private set var appVersionInfoCache = AppVersionInfoCache(this) - val connectionProxy = ConnectionProxy(this) + val connectionProxy = SmartDeferred(configureConnectionProxy()) val keyStatusListener = KeyStatusListener(daemon) val problemReport = MullvadProblemReport() var settingsListener = SettingsListener(this) @@ -80,6 +81,10 @@ class MainActivity : FragmentActivity() { } appVersionInfoCache.onCreate() + + if (intent.getBooleanExtra(KEY_SHOULD_CONNECT, false) ?: false) { + connectionProxy.awaitThen { connect() } + } } override fun onStart() { @@ -92,11 +97,7 @@ class MainActivity : FragmentActivity() { } override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { - if (resultCode == Activity.RESULT_OK) { - vpnPermission?.complete(true) - } else { - vpnPermission?.complete(false) - } + setVpnPermission(resultCode == Activity.RESULT_OK) } override fun onStop() { @@ -110,6 +111,8 @@ class MainActivity : FragmentActivity() { } override fun onDestroy() { + connectionProxy.cancel() + accountCache.onDestroy() appVersionInfoCache.onDestroy() keyStatusListener.onDestroy() @@ -136,19 +139,8 @@ class MainActivity : FragmentActivity() { } } - fun requestVpnPermission(): Deferred<Boolean> { - val intent = VpnService.prepare(this) - val request = CompletableDeferred<Boolean>() - - vpnPermission = request - - if (intent != null) { - startActivityForResult(intent, 0) - } else { - request.complete(true) - } - - return request + fun requestVpnPermission(intent: Intent) { + startActivityForResult(intent, 0) } fun quit() { @@ -163,6 +155,18 @@ class MainActivity : FragmentActivity() { } } + private fun configureConnectionProxy() = GlobalScope.async(Dispatchers.Default) { + service.await().connectionProxy.apply { + mainActivity = this@MainActivity + } + } + + private fun setVpnPermission(allow: Boolean) = GlobalScope.launch(Dispatchers.Default) { + connectionProxy.awaitThen { + vpnPermission.complete(allow) + } + } + private fun fetchSettings() = GlobalScope.async(Dispatchers.Default) { daemon.await().getSettings() } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt index 2c51a9ecde..6ecfdb3535 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt @@ -8,14 +8,13 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import android.app.Activity -import android.content.Context import android.content.Intent import android.net.VpnService import android.os.Binder import android.os.IBinder import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoFetcher +import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy import net.mullvad.mullvadvpn.model.TunConfig class MullvadVpnService : VpnService() { @@ -25,9 +24,12 @@ class MullvadVpnService : VpnService() { private lateinit var versionInfoFetcher: AppVersionInfoFetcher val daemon = startDaemon() + val connectionProxy = ConnectionProxy(this, daemon) + val notificationManager = ForegroundNotificationManager(this, connectionProxy) override fun onCreate() { versionInfoFetcher = AppVersionInfoFetcher(daemon, this) + notificationManager.onCreate() created.complete(Unit) } @@ -36,6 +38,8 @@ class MullvadVpnService : VpnService() { } override fun onDestroy() { + connectionProxy.onDestroy() + notificationManager.onDestroy() versionInfoFetcher.stop() daemon.cancel() created.cancel() @@ -70,6 +74,8 @@ class MullvadVpnService : VpnService() { inner class LocalBinder : Binder() { val daemon get() = this@MullvadVpnService.daemon + val connectionProxy + get() = this@MullvadVpnService.connectionProxy fun stop() { if (daemon.isCompleted) { diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt index 4a1849318c..f59387baa4 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt @@ -26,10 +26,11 @@ import net.mullvad.mullvadvpn.relaylist.RelayItem import net.mullvad.mullvadvpn.relaylist.RelayItemDividerDecoration import net.mullvad.mullvadvpn.relaylist.RelayList import net.mullvad.mullvadvpn.relaylist.RelayListAdapter +import net.mullvad.mullvadvpn.util.SmartDeferred class SelectLocationFragment : Fragment() { private lateinit var parentActivity: MainActivity - private lateinit var connectionProxy: ConnectionProxy + private lateinit var connectionProxy: SmartDeferred<ConnectionProxy> private lateinit var relayListListener: RelayListListener private lateinit var relayListContainer: ViewSwitcher @@ -129,7 +130,7 @@ class SelectLocationFragment : Fragment() { val keyStatus = parentActivity.keyStatusListener.keyStatus if (keyStatus == null || keyStatus is KeygenEvent.NewKey) { - connectionProxy.connect() + connectionProxy.awaitThen { connect() } } } } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt index e8be9d7228..47e79fb74c 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/WireguardKeyFragment.kt @@ -23,14 +23,17 @@ import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener import net.mullvad.mullvadvpn.model.KeygenEvent import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.mullvadvpn.util.SmartDeferred class WireguardKeyFragment : Fragment() { - private var TAG = "keyfragment"; - private var keyState: KeygenEvent? = null; - private var currentJob: Job? = null; - private var updateViewsJob: Job? = null; + private var keyState: KeygenEvent? = null + private var currentJob: Job? = null + private var updateViewsJob: Job? = null + private var tunnelStateListener: Int? = null + private var tunnelStateSubscriptionJob: Long? = null + private var tunnelState: TunnelState = TunnelState.Disconnected() private lateinit var parentActivity: MainActivity - private lateinit var connectionProxy: ConnectionProxy + private lateinit var connectionProxy: SmartDeferred<ConnectionProxy> private lateinit var keyStatusListener: KeyStatusListener private var generatingKey = false private var validatingKey = false @@ -69,16 +72,6 @@ class WireguardKeyFragment : Fragment() { updateViews() - connectionProxy.onUiStateChange = { _ -> - updateViewsJob?.cancel() - updateViewsJob = updateViewJob() - } - - keyStatusListener.onKeyStatusChange = { _ -> - updateViewsJob?.cancel() - updateViewsJob = updateViewJob() - } - return view } @@ -145,7 +138,7 @@ class WireguardKeyFragment : Fragment() { private fun setGenerateButton() { if (generatingKey) { showActionSpinner() - return; + return } actionSpinner.visibility = View.GONE actionButton.visibility = View.VISIBLE @@ -158,7 +151,7 @@ class WireguardKeyFragment : Fragment() { private fun setValidateButton() { if (validatingKey) { showActionSpinner() - return; + return } actionSpinner.visibility = View.GONE actionButton.visibility = View.VISIBLE @@ -174,7 +167,7 @@ class WireguardKeyFragment : Fragment() { } private fun drawNoConnectionState() { - when (connectionProxy.state) { + when (tunnelState) { is TunnelState.Connecting, is TunnelState.Disconnecting -> { statusMessage.setText(R.string.wireguard_key_connectivity) statusMessage.visibility = View.VISIBLE @@ -186,24 +179,24 @@ class WireguardKeyFragment : Fragment() { private fun onGenerateKeyPress() { currentJob?.cancel() - generatingKey = true; - validatingKey = false; + generatingKey = true + validatingKey = false updateViews() currentJob = GlobalScope.launch(Dispatchers.Main) { keyStatusListener.generateKey().join() - generatingKey = false; + generatingKey = false updateViews() } } private fun onValidateKeyPress() { currentJob?.cancel() - validatingKey = true; - generatingKey = false; + validatingKey = true + generatingKey = false updateViews() currentJob = GlobalScope.launch(Dispatchers.Main) { keyStatusListener.verifyKey().join() - validatingKey = false; + validatingKey = false when (val state = keyStatusListener.keyStatus) { is KeygenEvent.NewKey -> { if (state.verified == null) { @@ -216,20 +209,33 @@ class WireguardKeyFragment : Fragment() { } override fun onPause() { - connectionProxy.onUiStateChange = null + tunnelStateSubscriptionJob?.let { jobId -> + connectionProxy.cancelJob(jobId) + } + + tunnelStateListener?.let { listener -> + connectionProxy.awaitThen { + onUiStateChange.unsubscribe(listener) + } + } + keyStatusListener.onKeyStatusChange = null currentJob?.cancel() updateViewsJob?.cancel() - validatingKey = false; - generatingKey = false; + validatingKey = false + generatingKey = false super.onPause() } override fun onResume() { super.onResume() - connectionProxy.onUiStateChange = { _ -> - updateViewsJob?.cancel() - updateViewsJob = updateViewJob() + + tunnelStateSubscriptionJob = connectionProxy.awaitThen { + tunnelStateListener = onUiStateChange.subscribe { uiState -> + tunnelState = uiState + updateViewsJob?.cancel() + updateViewsJob = updateViewJob() + } } keyStatusListener.onKeyStatusChange = { _ -> diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt index 26abe8a106..2c46e34292 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/ConnectionProxy.kt @@ -1,16 +1,24 @@ package net.mullvad.mullvadvpn.dataproxy +import android.content.Context +import android.content.Intent +import android.net.VpnService + import kotlinx.coroutines.launch +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import net.mullvad.mullvadvpn.MainActivity +import net.mullvad.mullvadvpn.MullvadDaemon import net.mullvad.mullvadvpn.model.ActionAfterDisconnect import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.mullvadvpn.util.EventNotifier -class ConnectionProxy(val parentActivity: MainActivity) { - val daemon = parentActivity.daemon +class ConnectionProxy(val context: Context, val daemon: Deferred<MullvadDaemon>) { + var mainActivity: MainActivity? = null private var activeAction: Job? = null @@ -31,20 +39,17 @@ class ConnectionProxy(val parentActivity: MainActivity) { var uiState: TunnelState = TunnelState.Disconnected() private set(value) { field = value - onUiStateChange?.invoke(value) + onUiStateChange.notify(value) } - var onUiStateChange: ((TunnelState) -> Unit)? = null - set(value) { - field = value - value?.invoke(uiState) - } + var onUiStateChange = EventNotifier(uiState) + var vpnPermission = CompletableDeferred<Boolean>() fun connect() { if (anticipateConnectingState()) { cancelActiveAction() - val vpnPermission = parentActivity.requestVpnPermission() + requestVpnPermission() activeAction = GlobalScope.launch(Dispatchers.Default) { if (vpnPermission.await()) { @@ -68,6 +73,7 @@ class ConnectionProxy(val parentActivity: MainActivity) { } fun onDestroy() { + onUiStateChange.unsubscribeAll() attachListenerJob.cancel() detachListener() fetchInitialStateJob.cancel() @@ -100,6 +106,27 @@ class ConnectionProxy(val parentActivity: MainActivity) { } } + private fun requestVpnPermission() { + val intent = VpnService.prepare(context) + + vpnPermission = CompletableDeferred() + + if (intent == null) { + vpnPermission.complete(true) + } else { + val activity = mainActivity + + if (activity != null) { + activity.requestVpnPermission(intent) + } else { + val activityIntent = Intent(context, MainActivity::class.java) + .putExtra(MainActivity.KEY_SHOULD_CONNECT, true) + + context.startActivity(activityIntent) + } + } + } + private fun fetchInitialState() = GlobalScope.launch(Dispatchers.Default) { val initialState = daemon.await().getState() diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/util/EventNotifier.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/EventNotifier.kt new file mode 100644 index 0000000000..ca45ca96e6 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/EventNotifier.kt @@ -0,0 +1,42 @@ +package net.mullvad.mullvadvpn.util + +class EventNotifier<T>(private val initialValue: T) { + private val listeners = HashMap<Int, (T) -> Unit>() + + private var idCounter = 0 + private var latestEvent = initialValue + + fun notify(event: T) { + synchronized(this) { + for (listener in listeners.values) { + listener(event) + } + + latestEvent = event + } + } + + fun subscribe(listener: (T) -> Unit): Int { + synchronized(this) { + val id = idCounter + + idCounter += 1 + listeners.put(id, listener) + listener(latestEvent) + + return id + } + } + + fun unsubscribe(id: Int) { + synchronized(this) { + listeners.remove(id) + } + } + + fun unsubscribeAll() { + synchronized(this) { + listeners.clear() + } + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt new file mode 100644 index 0000000000..4934669d58 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/JobTracker.kt @@ -0,0 +1,46 @@ +package net.mullvad.mullvadvpn.util + +import kotlinx.coroutines.launch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job + +class JobTracker { + private val jobs = HashMap<Long, Job>() + + private var jobIdCounter = 0L + + fun newJob(job: Job): Long { + synchronized(jobs) { + val jobId = jobIdCounter + + jobIdCounter += 1 + + jobs.put(jobId, GlobalScope.launch(Dispatchers.Default) { + job.join() + + synchronized(jobs) { + jobs.remove(jobId) + } + }) + + return jobId + } + } + + fun cancelJob(jobId: Long) { + synchronized(jobs) { + jobs.remove(jobId)?.cancel() + } + } + + fun cancelAllJobs() { + synchronized(jobs) { + for (job in jobs.values) { + job.cancel() + } + + jobs.clear() + } + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt new file mode 100644 index 0000000000..a7732c08ef --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/util/SmartDeferred.kt @@ -0,0 +1,32 @@ +package net.mullvad.mullvadvpn.util + +import kotlinx.coroutines.launch +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job + +class SmartDeferred<T>(private val deferred: Deferred<T>) { + private val jobTracker = JobTracker() + + private var active = true + + fun awaitThen(action: T.() -> Unit): Long? { + if (active) { + return jobTracker.newJob(GlobalScope.launch(Dispatchers.Default) { + deferred.await().action() + }) + } else { + return null + } + } + + fun cancelJob(jobId: Long) { + jobTracker.cancelJob(jobId) + } + + fun cancel() { + active = false + jobTracker.cancelAllJobs() + } +} diff --git a/android/src/main/res/drawable-hdpi/notification.png b/android/src/main/res/drawable-hdpi/notification.png Binary files differnew file mode 100644 index 0000000000..a6aa370201 --- /dev/null +++ b/android/src/main/res/drawable-hdpi/notification.png diff --git a/android/src/main/res/drawable-mdpi/notification.png b/android/src/main/res/drawable-mdpi/notification.png Binary files differnew file mode 100644 index 0000000000..d1a7dc4d5f --- /dev/null +++ b/android/src/main/res/drawable-mdpi/notification.png diff --git a/android/src/main/res/drawable-xhdpi/notification.png b/android/src/main/res/drawable-xhdpi/notification.png Binary files differnew file mode 100644 index 0000000000..7ce258b490 --- /dev/null +++ b/android/src/main/res/drawable-xhdpi/notification.png diff --git a/android/src/main/res/drawable-xxhdpi/notification.png b/android/src/main/res/drawable-xxhdpi/notification.png Binary files differnew file mode 100644 index 0000000000..edebf879d3 --- /dev/null +++ b/android/src/main/res/drawable-xxhdpi/notification.png diff --git a/android/src/main/res/drawable-xxxhdpi/notification.png b/android/src/main/res/drawable-xxxhdpi/notification.png Binary files differnew file mode 100644 index 0000000000..b2b88998cb --- /dev/null +++ b/android/src/main/res/drawable-xxxhdpi/notification.png diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 50e4d43c79..986e0fc623 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -1,6 +1,13 @@ <resources> <string name="app_name">Mullvad VPN</string> + <string name="connecting">Connecting</string> + <string name="reconnecting">Reconnecting</string> + <string name="disconnecting">Disconnecting</string> + <string name="secured">Secured</string> + <string name="unsecured">Unsecured</string> + <string name="blocking_all_connections">Blocking all connections</string> + <string name="connecting_to_daemon">Connecting to Mullvad system service...</string> <string name="login_title">Login</string> |
