Skip to content

Commit

Permalink
Merge pull request #421 from eduvpn/feature/common_2.0
Browse files Browse the repository at this point in the history
Common 2.0.1 integration
  • Loading branch information
dzolnai authored Jun 6, 2024
2 parents 403294a + b0067f7 commit 6192d80
Show file tree
Hide file tree
Showing 24 changed files with 234 additions and 667 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,4 @@ class PreferencesServiceTest {
retrievedInstance.authenticationUrlTemplate
)
}

@Test
fun testLastKnownOrganizationListVersionSave() {
val version = 121_323L
_preferencesService.setLastKnownOrganizationListVersion(version)
val retrievedVersion = _preferencesService.getLastKnownOrganizationListVersion()
Assert.assertEquals(version, retrievedVersion)
}

@Test
fun testLastKnownServerListVersionSave() {
val version = 8_982_398L
_preferencesService.setLastKnownServerListVersion(version)
val retrievedVersion = _preferencesService.getLastKnownServerListVersion()
Assert.assertEquals(version, retrievedVersion)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,28 +85,6 @@ public void testInstanceSerialization() throws SerializerService.UnknownFormatEx
assertEquals(instance.getSupportContact(), deserializedInstance.getSupportContact());
}

@Test
public void testOrganizationListSerialization() throws SerializerService.UnknownFormatException {
Map<String, String> keywordsMap = new HashMap<>();
keywordsMap.put("en", "english keyword");
keywordsMap.put("de", "german keyword");
keywordsMap.put("nl", "dutch keyword");
Organization organization1 = new Organization("orgid-1", new TranslatableString("display name - 1"), new TranslatableString(keywordsMap), "https://server.info/url");
Organization organization2 = new Organization("orgid-2", new TranslatableString("display name - 2"), new TranslatableString("notthesamekeyword"), "https://server.info2/url");
Organization organization3 = new Organization("orgid-3", new TranslatableString("display name - 3"), new TranslatableString(), "http://server.info/url3");
List<Organization> organizations = Arrays.asList(organization1, organization2, organization3);
OrganizationList organizationList = new OrganizationList(12345L, organizations);
JSONObject serializedOrganizationList = _serializerService.serializeOrganizationList(organizationList);
OrganizationList deserializedOrganizationList = _serializerService.deserializeOrganizationList(serializedOrganizationList);
for (int i = 0; i < organizations.size(); ++i) {
assertEquals(organizations.get(i).getDisplayName(), deserializedOrganizationList.getOrganizationList().get(i).getDisplayName());
assertEquals(organizations.get(i).getKeywordList(), deserializedOrganizationList.getOrganizationList().get(i).getKeywordList());
assertEquals(organizations.get(i).getOrgId(), deserializedOrganizationList.getOrganizationList().get(i).getOrgId());
assertEquals(organizations.get(i).getSecureInternetHome(), deserializedOrganizationList.getOrganizationList().get(i).getSecureInternetHome());
assertEquals(organizationList.getVersion(), deserializedOrganizationList.getVersion());
}
}

/**
* Removes the milliseconds from a date. Required because the parser does not care about milliseconds.
*
Expand Down
11 changes: 5 additions & 6 deletions app/src/main/java/nl/eduvpn/app/adapter/OrganizationAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class OrganizationAdapter(private val onChangeLocationClickListener: (() -> Unit
sealed class OrganizationAdapterItem {
data class Header(@DrawableRes val icon: Int, @StringRes val headerName: Int, val includeLocationButton: Boolean = false) : OrganizationAdapterItem()
data class InstituteAccess(val server: Instance) : OrganizationAdapterItem()
data class SecureInternet(val server: Instance, val organization: Organization?) : OrganizationAdapterItem()
data class SecureInternet(val server: Instance) : OrganizationAdapterItem()
data class Organization(val organization: nl.eduvpn.app.entity.Organization) : OrganizationAdapterItem()
data class AddServer(val url: String) : OrganizationAdapterItem()
}

Expand All @@ -68,11 +69,9 @@ class OrganizationAdapter(private val onChangeLocationClickListener: (() -> Unit
if (item is OrganizationAdapterItem.InstituteAccess) {
holder.bind(item.server)
} else if (item is OrganizationAdapterItem.SecureInternet) {
if (item.organization != null) {
holder.bind(item.organization)
} else {
holder.bind(item.server)
}
holder.bind(item.server)
} else if (item is OrganizationAdapterItem.Organization) {
holder.bind(item.organization)
} else if (item is OrganizationAdapterItem.AddServer) {
holder.bind(item.url)
} else {
Expand Down
9 changes: 6 additions & 3 deletions app/src/main/java/nl/eduvpn/app/entity/Organization.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@

package nl.eduvpn.app.entity

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class Organization(
@SerialName("org_id")
val orgId: String,
val displayName: TranslatableString,
val keywordList: TranslatableString,
val secureInternetHome: String?
@SerialName("display_name")
val displayName: TranslatableString = TranslatableString(),
@SerialName("keyword_list")
val keywordList: TranslatableString = TranslatableString(),
)
8 changes: 6 additions & 2 deletions app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@

package nl.eduvpn.app.entity

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* A versioned list of organizations
*/
@Serializable
data class OrganizationList(
val version: Long,
val organizationList: List<Organization>
@SerialName("organization_list")
val organizationList: List<Organization>? = null
)
6 changes: 1 addition & 5 deletions app/src/main/java/nl/eduvpn/app/entity/ServerList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ import kotlinx.serialization.Serializable
*/
@Serializable
data class ServerList(

@SerialName("v")
val version: Long,

@SerialName("server_list")
val serverList: List<Instance>
val serverList: List<Instance>? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ package nl.eduvpn.app.fragment
import android.app.NotificationManager
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.viewModels
Expand All @@ -44,29 +41,21 @@ import nl.eduvpn.app.databinding.FragmentConnectionStatusBinding
import nl.eduvpn.app.entity.Profile
import nl.eduvpn.app.fragment.ServerSelectionFragment.Companion.newInstance
import nl.eduvpn.app.service.VPNConnectionService
import nl.eduvpn.app.service.VPNService
import nl.eduvpn.app.service.VPNService.VPNStatus
import nl.eduvpn.app.utils.ErrorDialog
import nl.eduvpn.app.utils.FormattingUtils
import nl.eduvpn.app.utils.Log
import nl.eduvpn.app.viewmodel.BaseConnectionViewModel
import nl.eduvpn.app.viewmodel.ConnectionStatusViewModel
import javax.inject.Inject

/**
* The fragment which displays the status of the current connection.
* Created by Daniel Zolnai on 2016-10-07.
*/
class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>() {

private val gracefulDisconnectHandler = Handler(Looper.getMainLooper())

private var isAutomaticCheckChange = false
private var skipNextDisconnect = true

@Inject
protected lateinit var vpnService: VPNService

override val layout = R.layout.fragment_connection_status

private val viewModel by viewModels<ConnectionStatusViewModel> { viewModelFactory }
Expand Down Expand Up @@ -96,14 +85,14 @@ class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>()
bc?.bytesOut
)
}.asLiveData()
binding.protocol = vpnService.getProtocol()
binding.protocol = viewModel.protocol
binding.ips = viewModel.ipFLow.asLiveData()
binding.connectionSwitch.setOnCheckedChangeListener { _, isChecked ->
if (isAutomaticCheckChange) {
return@setOnCheckedChangeListener
}
if (!isChecked) {
disconnect()
viewModel.disconnect(activity)
} else {
// Get the config again, and connect again
viewModel.reconnectWithCurrentProfile()
Expand Down Expand Up @@ -142,7 +131,7 @@ class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>()
}
}
binding.renewSession.setOnClickListener {
disconnect()
viewModel.disconnect(activity)
viewModel.renewSession()
}
viewModel.connectionParentAction.observe(viewLifecycleOwner) { parentAction ->
Expand All @@ -154,7 +143,7 @@ class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>()
R.string.error_certificate_expired_title,
R.string.error_certificate_expired_message
)
disconnect()
viewModel.disconnect(activity)
dialog?.listener = object : ErrorDialog.ErrorDialogFragment.Listener {
override fun onDismiss() {
returnToHome()
Expand Down Expand Up @@ -205,55 +194,45 @@ class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>()
}
}
viewModel.timer.observe(viewLifecycleOwner, updateCertExpiryObserver)
val vpnStatusObserver = { vpnStatus: VPNStatus ->
viewModel.notifyVpnStatus(vpnStatus)
binding.connectionStatus.setText(VPNConnectionService.vpnStatusToStringID(vpnStatus))
when (vpnStatus) {
viewModel.vpnStatus.observe(viewLifecycleOwner) { status ->
binding.connectionStatus.setText(VPNConnectionService.vpnStatusToStringID(status))
when (status) {
VPNStatus.CONNECTED -> {
binding.connectionStatusIcon.setImageResource(R.drawable.ic_connection_status_connected)
skipNextDisconnect = false
setToggleCheckedWithoutAction(true)
viewModel.isInDisconnectMode.value = false
}
VPNStatus.CONNECTING -> {
binding.connectionStatusIcon.setImageResource(R.drawable.ic_connection_status_connecting)
skipNextDisconnect = false
setToggleCheckedWithoutAction(true)
viewModel.isInDisconnectMode.value = false
}
VPNStatus.PAUSED -> {
binding.connectionStatusIcon.setImageResource(R.drawable.ic_connection_status_connecting)
skipNextDisconnect = false
setToggleCheckedWithoutAction(true)
viewModel.isInDisconnectMode.value = false
}
VPNStatus.DISCONNECTED -> {
binding.connectionStatusIcon.setImageResource(R.drawable.ic_connection_status_disconnected)
if (!skipNextDisconnect) {
// The first disconnect can mess a bit with the UI so we skip this part in special cases
setToggleCheckedWithoutAction(false)
}
gracefulDisconnectHandler.removeCallbacksAndMessages(null)
viewModel.isInDisconnectMode.value = true
}
VPNStatus.FAILED -> {
skipNextDisconnect = false
val message =
getString(R.string.error_while_connecting, vpnService.getErrorString())
getString(R.string.error_while_connecting, viewModel.getVpnErrorString())
ErrorDialog.show(
requireActivity(),
R.string.error_dialog_title_unable_to_connect,
message
)
binding.connectionStatusIcon.setImageResource(R.drawable.ic_connection_status_disconnected)
setToggleCheckedWithoutAction(false)
viewModel.isInDisconnectMode.value = true
}
}
}
// Update the icon immediately
vpnStatusObserver(vpnService.getStatus())
vpnService.observe(viewLifecycleOwner, vpnStatusObserver)
}

private fun setToggleCheckedWithoutAction(isChecked: Boolean) {
Expand All @@ -263,7 +242,7 @@ class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>()
}

fun returnToHome() {
disconnect()
viewModel.disconnect(activity)
val activity = activity as MainActivity?
if (activity != null && !activity.isFinishing) {
activity.setBackNavigationEnabled(false)
Expand Down Expand Up @@ -304,31 +283,4 @@ class ConnectionStatusFragment : BaseFragment<FragmentConnectionStatusBinding>()
}
}
}

private fun disconnect(retryCount: Int = 0) {
val isConnecting = vpnService.getStatus() == VPNStatus.CONNECTING
viewModel.disconnectWithCall(vpnService)
if (isConnecting) {
// In this case, if we call disconnect, the process can be killed.
// That means we won't get any notification from the disconnect event.
// So we add a timer which waits for the disconnect event. If not received, we assume the process was killed.
gracefulDisconnectHandler.postDelayed({
if (activity?.isFinishing != false) {
Log.i(TAG, "Nothing to do, already finishing activity.")
return@postDelayed
}
Log.i(TAG, "No disconnect event received from VPN within $WAIT_FOR_DISCONNECT_UNTIL_MS milliseconds. Assuming process died.")
if (retryCount < 3) {
disconnect(retryCount + 1)
} else {
viewModel.isInDisconnectMode.value = true
}
}, WAIT_FOR_DISCONNECT_UNTIL_MS.toLong())
}
}

companion object {
private const val WAIT_FOR_DISCONNECT_UNTIL_MS = 1_000
private val TAG = ConnectionStatusFragment::class.java.name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,16 @@

package nl.eduvpn.app.fragment

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import nl.eduvpn.app.EduVPNApplication
import nl.eduvpn.app.MainActivity
import nl.eduvpn.app.R
import nl.eduvpn.app.adapter.OrganizationAdapter
import nl.eduvpn.app.base.BaseFragment
Expand Down Expand Up @@ -77,9 +73,12 @@ class OrganizationSelectionFragment : BaseFragment<FragmentOrganizationSelection
if (item is OrganizationAdapter.OrganizationAdapterItem.Header) {
return@setOnItemClickListener
} else if (item is OrganizationAdapter.OrganizationAdapterItem.SecureInternet) {
viewModel.selectOrganizationAndInstance(item.organization, item.server)
viewModel.discoverApi(item.server)
} else if (item is OrganizationAdapter.OrganizationAdapterItem.InstituteAccess) {
viewModel.selectOrganizationAndInstance(null, item.server)
viewModel.discoverApi(item.server)
} else if (item is OrganizationAdapter.OrganizationAdapterItem.Organization) {
val organization = item.organization
viewModel.discoverApi(Instance(baseURI = organization.orgId, displayName = organization.displayName, authorizationType = AuthorizationType.Distributed))
} else if (item is OrganizationAdapter.OrganizationAdapterItem.AddServer) {
val customUrl =
if (item.url.startsWith("http://") || item.url.startsWith("https://")) {
Expand Down Expand Up @@ -120,8 +119,10 @@ class OrganizationSelectionFragment : BaseFragment<FragmentOrganizationSelection
// Trigger initial status
it.onChanged()
}
viewModel.adapterItems.observe(viewLifecycleOwner) {
adapter.submitList(it)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.adapterItems.collect {
adapter.submitList(it)
}
}
viewModel.parentAction.observe(viewLifecycleOwner) { parentAction ->
when (parentAction) {
Expand Down
Loading

0 comments on commit 6192d80

Please sign in to comment.