summaryrefslogtreecommitdiffhomepage
path: root/android/src
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-06-17 18:10:59 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-06-17 18:10:59 -0300
commit76520c1f7335e514eadd204e12c9b91f914a81df (patch)
tree24a6bcfb2522191441f903a1afbe86bcbda45802 /android/src
parent4df5577ee29a0ba4943f0a9db9293aee663bbb7d (diff)
parentc9c01b7ee6526a5ea5dab207fa9a5af41b5b6c4d (diff)
downloadmullvadvpn-76520c1f7335e514eadd204e12c9b91f914a81df.tar.xz
mullvadvpn-76520c1f7335e514eadd204e12c9b91f914a81df.zip
Merge branch 'listen-for-relay-list-updates'
Diffstat (limited to 'android/src')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt31
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt74
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt36
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListListener.kt113
-rw-r--r--android/src/main/res/anim/fade_in.xml8
-rw-r--r--android/src/main/res/anim/fade_out.xml8
-rw-r--r--android/src/main/res/layout/select_location.xml23
8 files changed, 234 insertions, 64 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
index 71416c92c4..e21c7b6682 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
@@ -20,27 +20,21 @@ 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.relaylist.RelayListListener
class MainActivity : FragmentActivity() {
var asyncDaemon = CompletableDeferred<MullvadDaemon>()
val daemon
get() = runBlocking { asyncDaemon.await() }
- var asyncRelayList: Deferred<RelayList> = fetchRelayList()
- private set
- val relayList: RelayList
- get() = runBlocking { asyncRelayList.await() }
-
var asyncSettings = fetchSettings()
private set
val settings
get() = runBlocking { asyncSettings.await() }
val accountCache = AccountCache(this)
+ var relayListListener = RelayListListener(this)
- var selectedRelayItem: RelayItem? = null
-
- private val restoreSelectedRelayListItemJob = restoreSelectedRelayListItem()
private var waitForDaemonJob: Job? = null
private val serviceConnection = object : ServiceConnection {
@@ -84,11 +78,10 @@ class MainActivity : FragmentActivity() {
override fun onDestroy() {
accountCache.onDestroy()
+ relayListListener.onDestroy()
- restoreSelectedRelayListItemJob.cancel()
waitForDaemonJob?.cancel()
asyncSettings.cancel()
- asyncRelayList.cancel()
asyncDaemon.cancel()
super.onDestroy()
@@ -122,25 +115,7 @@ class MainActivity : FragmentActivity() {
}
}
- private fun fetchRelayList() = GlobalScope.async(Dispatchers.Default) {
- RelayList(asyncDaemon.await().getRelayLocations())
- }
-
private fun fetchSettings() = GlobalScope.async(Dispatchers.Default) {
asyncDaemon.await().getSettings()
}
-
- private fun restoreSelectedRelayListItem() = GlobalScope.launch(Dispatchers.Default) {
- val relaySettings = asyncSettings.await().relaySettings
-
- when (relaySettings) {
- is RelaySettings.CustomTunnelEndpoint -> selectedRelayItem = null
- is RelaySettings.RelayConstraints -> {
- val location = relaySettings.location
- val relayList = asyncRelayList.await()
-
- selectedRelayItem = relayList.findItemForLocation(location, true)
- }
- }
- }
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
index d16ba06a1a..a777611c84 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
@@ -14,6 +14,7 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
initialize(vpnService)
}
+ var onRelayListChange: ((RelayList) -> Unit)? = null
var onTunnelStateChange: ((TunnelStateTransition) -> Unit)? = null
external fun connect()
@@ -30,6 +31,10 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
private external fun initialize(vpnService: MullvadVpnService)
+ private fun notifyRelayListEvent(relayList: RelayList) {
+ onRelayListChange?.invoke(relayList)
+ }
+
private fun notifyTunnelStateEvent(event: TunnelStateTransition) {
onTunnelStateChange?.invoke(event)
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt
index 5ca767c19e..4255dc3060 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SelectLocationFragment.kt
@@ -3,7 +3,9 @@ package net.mullvad.mullvadvpn
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
@@ -12,6 +14,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
+import android.widget.ViewSwitcher
import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.LocationConstraint
@@ -20,8 +23,37 @@ 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.relaylist.RelayListListener
class SelectLocationFragment : Fragment() {
+ private lateinit var parentActivity: MainActivity
+ private lateinit var relayListListener: RelayListListener
+
+ private lateinit var relayListContainer: ViewSwitcher
+
+ private val relayListAdapter = RelayListAdapter()
+
+ private var updateRelayListJob: Job? = null
+
+ init {
+ relayListAdapter.onSelect = { relayItem ->
+ relayListListener.selectedRelayItem = relayItem
+ updateLocationConstraint()
+ close()
+ }
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+
+ parentActivity = context as MainActivity
+ relayListListener = parentActivity.relayListListener
+
+ relayListListener.onRelayListChange = { relayList, selectedItem ->
+ updateRelayListJob = updateRelayList(relayList, selectedItem)
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -31,26 +63,25 @@ class SelectLocationFragment : Fragment() {
view.findViewById<ImageButton>(R.id.close).setOnClickListener { close() }
+ relayListContainer = view.findViewById<ViewSwitcher>(R.id.relay_list_container)
+ relayListContainer.showNext()
+
configureRelayList(view.findViewById<RecyclerView>(R.id.relay_list))
return view
}
+ override fun onDestroyView() {
+ updateRelayListJob?.cancel()
+
+ super.onDestroyView()
+ }
+
fun close() {
activity?.onBackPressed()
}
private fun configureRelayList(relayList: RecyclerView) {
- val parentActivity = activity as MainActivity
- val relayListAdapter =
- RelayListAdapter(parentActivity.relayList, parentActivity.selectedRelayItem)
-
- relayListAdapter.onSelect = { relayItem ->
- parentActivity.selectedRelayItem = relayItem
- updateLocationConstraint(relayItem)
- close()
- }
-
relayList.apply {
layoutManager = LinearLayoutManager(context!!)
adapter = relayListAdapter
@@ -59,19 +90,22 @@ class SelectLocationFragment : Fragment() {
}
}
- private fun updateLocationConstraint(relayItem: RelayItem?) =
- GlobalScope.launch(Dispatchers.Default) {
- val parentActivity = activity as MainActivity
- var constraint: Constraint<LocationConstraint>
-
- if (relayItem == null) {
- constraint = Constraint.Any()
- } else {
- constraint = Constraint.Only(relayItem.location)
- }
+ private fun updateLocationConstraint() = GlobalScope.launch(Dispatchers.Default) {
+ val constraint = relayListListener.selectedRelayLocation
parentActivity.asyncDaemon.await().updateRelaySettings(
RelaySettingsUpdate.RelayConstraintsUpdate(constraint)
)
}
+
+ private fun updateRelayList(relayList: RelayList, selectedItem: RelayItem?) =
+ GlobalScope.launch(Dispatchers.Main) {
+ relayListAdapter.onRelayListChange(relayList, selectedItem)
+
+ if (relayList.countries.isEmpty()) {
+ relayListContainer.showPrevious()
+ } else if (relayListContainer.displayedChild == 0) {
+ relayListContainer.showNext()
+ }
+ }
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt
index de3336a4ea..60ad96171c 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListAdapter.kt
@@ -9,10 +9,9 @@ import android.view.ViewGroup
import net.mullvad.mullvadvpn.R
-class RelayListAdapter(
- private val relayList: RelayList,
- private var selectedItem: RelayItem?
-) : Adapter<RelayItemHolder>() {
+class RelayListAdapter : Adapter<RelayItemHolder>() {
+ private var relayList: RelayList? = null
+ private var selectedItem: RelayItem? = null
private val activeIndices = LinkedList<WeakReference<RelayListAdapterPosition>>()
private var selectedItemHolder: RelayItemHolder? = null
@@ -29,23 +28,34 @@ class RelayListAdapter(
}
override fun onBindViewHolder(holder: RelayItemHolder, position: Int) {
- var remaining = position
+ val relayList = this.relayList
- for (country in relayList.countries) {
- val itemOrCount = country.getItem(remaining)
+ if (relayList != null) {
+ var remaining = position
- when (itemOrCount) {
- is GetItemResult.Item -> {
- bindHolderToItem(holder, itemOrCount.item, position)
- return
+ for (country in relayList.countries) {
+ val itemOrCount = country.getItem(remaining)
+
+ when (itemOrCount) {
+ is GetItemResult.Item -> {
+ bindHolderToItem(holder, itemOrCount.item, position)
+ return
+ }
+ is GetItemResult.Count -> remaining -= itemOrCount.count
}
- is GetItemResult.Count -> remaining -= itemOrCount.count
}
}
}
override fun getItemCount() =
- relayList.countries.map { country -> country.visibleItemCount }.sum()
+ relayList?.countries?.map { country -> country.visibleItemCount }?.sum() ?: 0
+
+ fun onRelayListChange(relayList: RelayList, selectedItem: RelayItem?) {
+ this.relayList = relayList
+ this.selectedItem = selectedItem
+
+ notifyDataSetChanged()
+ }
fun selectItem(item: RelayItem?, holder: RelayItemHolder?) {
selectedItemHolder?.selected = false
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListListener.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListListener.kt
new file mode 100644
index 0000000000..87624b116c
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListListener.kt
@@ -0,0 +1,113 @@
+package net.mullvad.mullvadvpn.relaylist
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+
+import net.mullvad.mullvadvpn.MainActivity
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.LocationConstraint
+import net.mullvad.mullvadvpn.model.RelaySettings
+import net.mullvad.mullvadvpn.MullvadDaemon
+
+class RelayListListener(val parentActivity: MainActivity) {
+ private val daemon = CompletableDeferred<MullvadDaemon>()
+ private val setUpJob = setUp()
+
+ private var relayList: RelayList? = null
+ private var relaySettings: RelaySettings? = null
+
+ var selectedRelayItem: RelayItem? = null
+ set(value) {
+ field = value
+ updateRelaySettings()
+ }
+
+ val selectedRelayLocation: Constraint<LocationConstraint>
+ get() {
+ val location = selectedRelayItem?.location
+
+ if (location == null) {
+ return Constraint.Any()
+ } else {
+ return Constraint.Only(location)
+ }
+ }
+
+ var onRelayListChange: ((RelayList, RelayItem?) -> Unit)? = null
+ set(value) {
+ field = value
+
+ synchronized(this) {
+ val relayList = this.relayList
+
+ if (relayList != null) {
+ value?.invoke(relayList, selectedRelayItem)
+ }
+ }
+ }
+
+ fun onDestroy() {
+ setUpJob.cancel()
+
+ if (daemon.isActive) {
+ daemon.cancel()
+ } else {
+ daemon.getCompleted().onRelayListChange = null
+ }
+ }
+
+ private fun setUp() = GlobalScope.launch(Dispatchers.Default) {
+ daemon.complete(parentActivity.asyncDaemon.await())
+
+ setUpListener()
+ fetchInitialRelayList()
+ }
+
+ private suspend fun setUpListener() {
+ daemon.await().onRelayListChange = { relayLocations ->
+ relayListChanged(RelayList(relayLocations))
+ }
+ }
+
+ private suspend fun fetchInitialRelayList() {
+ val relayLocations = daemon.await().getRelayLocations()
+
+ relaySettings = parentActivity.asyncSettings.await().relaySettings
+
+ synchronized(this) {
+ if (relayList == null) {
+ relayListChanged(RelayList(relayLocations))
+ }
+ }
+ }
+
+ private fun relayListChanged(newRelayList: RelayList) {
+ synchronized(this) {
+ relayList = newRelayList
+ selectedRelayItem = findSelectedRelayItem()
+
+ onRelayListChange?.invoke(newRelayList, selectedRelayItem)
+ }
+ }
+
+ private fun findSelectedRelayItem(): RelayItem? {
+ val relaySettings = this.relaySettings
+
+ when (relaySettings) {
+ is RelaySettings.CustomTunnelEndpoint -> return null
+ is RelaySettings.RelayConstraints -> {
+ val location = relaySettings.location
+
+ return relayList?.findItemForLocation(location, true)
+ }
+ }
+
+ return null
+ }
+
+ private fun updateRelaySettings() {
+ relaySettings = RelaySettings.RelayConstraints(selectedRelayLocation)
+ }
+}
diff --git a/android/src/main/res/anim/fade_in.xml b/android/src/main/res/anim/fade_in.xml
new file mode 100644
index 0000000000..fc857c772d
--- /dev/null
+++ b/android/src/main/res/anim/fade_in.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:duration="450"
+ />
+</set>
diff --git a/android/src/main/res/anim/fade_out.xml b/android/src/main/res/anim/fade_out.xml
new file mode 100644
index 0000000000..c1d91f102b
--- /dev/null
+++ b/android/src/main/res/anim/fade_out.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:duration="450"
+ />
+</set>
diff --git a/android/src/main/res/layout/select_location.xml b/android/src/main/res/layout/select_location.xml
index a372b32a9f..3a07326b48 100644
--- a/android/src/main/res/layout/select_location.xml
+++ b/android/src/main/res/layout/select_location.xml
@@ -37,10 +37,27 @@
android:textSize="13sp"
android:text="@string/select_location_description"
/>
- <android.support.v7.widget.RecyclerView android:id="@+id/relay_list"
+ <ViewSwitcher android:id="@+id/relay_list_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:scrollbars="vertical"
- />
+ android:inAnimation="@anim/fade_in"
+ android:outAnimation="@anim/fade_out"
+ >
+ <ProgressBar
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:indeterminateDuration="600"
+ android:indeterminateDrawable="@drawable/icon_spinner"
+ android:visibility="invisible"
+ />
+ <android.support.v7.widget.RecyclerView android:id="@+id/relay_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="vertical"
+ />
+ </ViewSwitcher>
</LinearLayout>