Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Common 2.0.1 integration #421

Merged
merged 8 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading