From 0284bc489e1ee547229ad3547866fd0a328b3124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Wed, 5 Jun 2024 15:32:02 +0200 Subject: [PATCH 1/8] Bump common to 2.0.0 --- common/libs/eduvpn-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/libs/eduvpn-common b/common/libs/eduvpn-common index 8f9a1ec5..3d47c630 160000 --- a/common/libs/eduvpn-common +++ b/common/libs/eduvpn-common @@ -1 +1 @@ -Subproject commit 8f9a1ec5fb489af88c6e53f592bac3cccfb77f67 +Subproject commit 3d47c63005ad962951080370a647914ab7c34e94 From b01441499848d63fb654a346d5e3cf768fb4d9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Wed, 5 Jun 2024 16:40:35 +0200 Subject: [PATCH 2/8] Make the app work again with common version 2.0 --- .../eduvpn/app/adapter/OrganizationAdapter.kt | 11 ++- .../nl/eduvpn/app/entity/OrganizationList.kt | 5 +- .../java/nl/eduvpn/app/entity/ServerList.kt | 4 -- .../fragment/OrganizationSelectionFragment.kt | 13 ++-- .../eduvpn/app/service/PreferencesService.kt | 68 ------------------ .../eduvpn/app/service/SerializerService.kt | 4 +- .../nl/eduvpn/app/utils/FormattingUtils.kt | 2 +- .../OrganizationSelectionViewModel.kt | 70 ++++--------------- .../app/viewmodel/ServerSelectionViewModel.kt | 2 +- common/src/main/cpp/jni.cpp | 6 +- 10 files changed, 33 insertions(+), 152 deletions(-) diff --git a/app/src/main/java/nl/eduvpn/app/adapter/OrganizationAdapter.kt b/app/src/main/java/nl/eduvpn/app/adapter/OrganizationAdapter.kt index 1b7a2b1c..eeeecd51 100644 --- a/app/src/main/java/nl/eduvpn/app/adapter/OrganizationAdapter.kt +++ b/app/src/main/java/nl/eduvpn/app/adapter/OrganizationAdapter.kt @@ -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() } @@ -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 { diff --git a/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt b/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt index 0a21d856..b3dfefd9 100644 --- a/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt +++ b/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt @@ -21,7 +21,4 @@ package nl.eduvpn.app.entity /** * A versioned list of organizations */ -data class OrganizationList( - val version: Long, - val organizationList: List -) \ No newline at end of file +data class OrganizationList(val organizationList: List) \ No newline at end of file diff --git a/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt b/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt index a5a9575c..f9f85edc 100644 --- a/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt +++ b/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt @@ -26,10 +26,6 @@ import kotlinx.serialization.Serializable */ @Serializable data class ServerList( - - @SerialName("v") - val version: Long, - @SerialName("server_list") val serverList: List ) diff --git a/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt b/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt index f136c6e4..c568228a 100644 --- a/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt +++ b/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt @@ -17,20 +17,14 @@ 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.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar 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 @@ -77,9 +71,12 @@ class OrganizationSelectionFragment : BaseFragment = ArrayList() - val version = jsonObject.getLong("v") val itemsList = jsonObject.getJSONArray("organization_list") for (i in 0 until itemsList.length()) { val serializedItem = itemsList.getJSONObject(i) val organization = deserializeOrganization(serializedItem) result.add(organization) } - OrganizationList(version, result) + OrganizationList(result) } catch (ex: JSONException) { throw UnknownFormatException(ex) } @@ -250,7 +249,6 @@ class SerializerService { organizationsArray.put(serializeOrganization(organization)) } result.put("organization_list", organizationsArray) - result.put("v", organizationList.version) result } catch (ex: JSONException) { throw UnknownFormatException(ex) diff --git a/app/src/main/java/nl/eduvpn/app/utils/FormattingUtils.kt b/app/src/main/java/nl/eduvpn/app/utils/FormattingUtils.kt index 499bd01f..dff8952a 100644 --- a/app/src/main/java/nl/eduvpn/app/utils/FormattingUtils.kt +++ b/app/src/main/java/nl/eduvpn/app/utils/FormattingUtils.kt @@ -124,7 +124,7 @@ object FormattingUtils { return if (instance.isCustom) { val uri = URI.create(instance.sanitizedBaseURI) uri.host - } else if (instance.countryCode != null) { + } else if (!instance.countryCode.isNullOrEmpty()) { Locale("en", instance.countryCode).getDisplayCountry(Constants.ENGLISH_LOCALE) } else { instance.displayName.bestTranslation diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt index 99611b39..1c291dfd 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt @@ -88,7 +88,7 @@ class OrganizationSelectionViewModel @Inject constructor( } else { // We can't show any organization servers (secure internet), user needs to reset to switch. connectionState.postValue(ConnectionState.FetchingServerList) - CompletableDeferred(OrganizationList(-1L, emptyList())) + CompletableDeferred(OrganizationList(emptyList())) } val cachedServerList = preferencesService.getServerList() val serverListDeferred = if (cachedServerList != null) { @@ -97,45 +97,15 @@ class OrganizationSelectionViewModel @Inject constructor( async { organizationService.fetchServerList() } } - val lastKnownOrganizationVersion = - preferencesService.getLastKnownOrganizationListVersion() - val lastKnownServerListVersion = preferencesService.getLastKnownServerListVersion() - val organizationList = runCatchingCoroutine { organizationListDeferred.await() }.getOrElse { Log.w(TAG, "Organizations call has failed!", it) - OrganizationList(-1L, emptyList()) + OrganizationList(emptyList()) } val serverList = runCatchingCoroutine { serverListDeferred.await() }.getOrElse { Log.w(TAG, "Server list call has failed!", it) - ServerList(-1L, emptyList()) - } - if (serverList.version > 0 && lastKnownServerListVersion != null && lastKnownServerListVersion > serverList.version) { - organizations.value = emptyList() - instituteAccessServers.value = emptyList() - secureInternetServers.value = emptyList() - connectionState.value = ConnectionState.Ready - _parentAction.value = ParentAction.DisplayError( - R.string.error_server_list_version_check_title, - context.getString(R.string.error_server_list_version_check_message) - ) - } else if (organizationList.version > 0 && lastKnownOrganizationVersion != null && lastKnownOrganizationVersion > organizationList.version) { - organizations.value = emptyList() - instituteAccessServers.value = emptyList() - secureInternetServers.value = emptyList() - connectionState.value = ConnectionState.Ready - _parentAction.value = ParentAction.DisplayError( - R.string.error_organization_list_version_check_title, - context.getString(R.string.error_organization_list_version_check_message) - ) - } - - if (organizationList.version > 0) { - preferencesService.setLastKnownOrganizationListVersion(organizationList.version) - } - if (serverList.version > 0) { - preferencesService.setLastKnownServerListVersion(serverList.version) + ServerList(emptyList()) } val sortedOrganizations = organizationList.organizationList.sortedWith( @@ -194,23 +164,21 @@ class OrganizationSelectionViewModel @Inject constructor( ) return@map resultList } + val instituteAccessServersFiltered = instituteAccessServers.filter { matchesServer(searchText, it.server.displayName, it.server.keywords) } - val secureInternetServersFiltered = organizations.filter { - matchesServer(searchText, it.displayName, it.keywordList) - }.mapNotNull { organization -> - val matchingServer = secureInternetServers - .firstOrNull { - it.baseURI == organization.secureInternetHome - } - if (matchingServer != null) { - OrganizationAdapter.OrganizationAdapterItem.SecureInternet( - matchingServer, - organization - ) - } else { - null + val secureInternetServersFiltered = if (historyService.hasSecureInternetServer()) { + secureInternetServers.filter { + matchesServer(searchText, it.displayName, it.keywords) + }.map { + OrganizationAdapter.OrganizationAdapterItem.SecureInternet(it) + } + } else { + organizations.filter { + matchesServer(searchText, it.displayName, it.keywordList) + }.map { + OrganizationAdapter.OrganizationAdapterItem.Organization(it) } } if (instituteAccessServersFiltered.isNotEmpty()) { @@ -239,14 +207,6 @@ class OrganizationSelectionViewModel @Inject constructor( } } - fun selectOrganizationAndInstance(organization: Organization?, instance: Instance) { - if (organization == null) { - discoverApi(instance) - } else { - discoverApi(Instance(baseURI = organization.orgId, displayName = organization.displayName, authorizationType = AuthorizationType.Distributed)) - } - } - companion object { private val TAG = OrganizationSelectionViewModel::class.java.name } diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt index 4d2cd0a8..8e488d7a 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt @@ -141,7 +141,7 @@ class ServerSelectionViewModel @Inject constructor( } if (distributedInstance != null) { result += OrganizationAdapter.OrganizationAdapterItem.Header(R.drawable.ic_secure_internet, R.string.header_secure_internet, includeLocationButton = true) - result += OrganizationAdapter.OrganizationAdapterItem.SecureInternet(distributedInstance, null) + result += OrganizationAdapter.OrganizationAdapterItem.SecureInternet(distributedInstance) } if (customServers.isNotEmpty()) { result += OrganizationAdapter.OrganizationAdapterItem.Header(R.drawable.ic_server, R.string.header_other_servers) diff --git a/common/src/main/cpp/jni.cpp b/common/src/main/cpp/jni.cpp index 0e8ea799..ca714357 100644 --- a/common/src/main/cpp/jni.cpp +++ b/common/src/main/cpp/jni.cpp @@ -203,14 +203,16 @@ Java_org_eduvpn_common_GoBackend_register(JNIEnv *env, jobject /* this */, jstri extern "C" JNIEXPORT jobject JNICALL Java_org_eduvpn_common_GoBackend_discoverOrganizations(JNIEnv *env, jobject /* this */) { uintptr_t cookie = CookieNew(); - DiscoOrganizations_return organizationsReturn = DiscoOrganizations(cookie); + const char *searchString = ""; + DiscoOrganizations_return organizationsReturn = DiscoOrganizations(cookie, (char *)searchString); return CreateDataErrorTuple(env, organizationsReturn.r0, organizationsReturn.r1); } extern "C" JNIEXPORT jobject JNICALL Java_org_eduvpn_common_GoBackend_discoverServers(JNIEnv *env, jobject /* this */) { uintptr_t cookie = CookieNew(); - DiscoServers_return serversReturn = DiscoServers(cookie); + const char *searchString = ""; + DiscoServers_return serversReturn = DiscoServers(cookie, (char *)searchString); CookieDelete(cookie); return CreateDataErrorTuple(env, serversReturn.r0, serversReturn.r1); From 24aaec76b2598375d587bc762dea1f0409f985f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Thu, 6 Jun 2024 10:08:13 +0200 Subject: [PATCH 3/8] Search using the common library This commit removes the local searching, since the common library will be doing that for us from now on. Also I removed the custom JSON [de]serializing code, since we do not need to cache the lists anymore. Because of these changes, we were able to clean up quite some code on the Android side --- .../java/nl/eduvpn/app/entity/Organization.kt | 9 +- .../nl/eduvpn/app/entity/OrganizationList.kt | 9 +- .../java/nl/eduvpn/app/entity/ServerList.kt | 2 +- .../fragment/OrganizationSelectionFragment.kt | 8 +- .../nl/eduvpn/app/service/BackendService.kt | 8 +- .../nl/eduvpn/app/service/HistoryService.java | 9 - .../eduvpn/app/service/OrganizationService.kt | 11 +- .../eduvpn/app/service/PreferencesService.kt | 52 ---- .../eduvpn/app/service/SerializerService.kt | 156 +----------- .../OrganizationSelectionViewModel.kt | 223 +++++++----------- .../app/viewmodel/ServerSelectionViewModel.kt | 12 +- common/src/main/cpp/jni.cpp | 8 +- .../java/org/eduvpn/common/GoBackend.java | 4 +- 13 files changed, 125 insertions(+), 386 deletions(-) diff --git a/app/src/main/java/nl/eduvpn/app/entity/Organization.kt b/app/src/main/java/nl/eduvpn/app/entity/Organization.kt index b000368b..4f2bebec 100644 --- a/app/src/main/java/nl/eduvpn/app/entity/Organization.kt +++ b/app/src/main/java/nl/eduvpn/app/entity/Organization.kt @@ -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(), ) \ No newline at end of file diff --git a/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt b/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt index b3dfefd9..e7d5f787 100644 --- a/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt +++ b/app/src/main/java/nl/eduvpn/app/entity/OrganizationList.kt @@ -18,7 +18,14 @@ package nl.eduvpn.app.entity +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + /** * A versioned list of organizations */ -data class OrganizationList(val organizationList: List) \ No newline at end of file +@Serializable +data class OrganizationList( + @SerialName("organization_list") + val organizationList: List? = null +) \ No newline at end of file diff --git a/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt b/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt index f9f85edc..2037a527 100644 --- a/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt +++ b/app/src/main/java/nl/eduvpn/app/entity/ServerList.kt @@ -27,5 +27,5 @@ import kotlinx.serialization.Serializable @Serializable data class ServerList( @SerialName("server_list") - val serverList: List + val serverList: List? = null ) diff --git a/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt b/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt index c568228a..833bcf82 100644 --- a/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt +++ b/app/src/main/java/nl/eduvpn/app/fragment/OrganizationSelectionFragment.kt @@ -20,10 +20,12 @@ package nl.eduvpn.app.fragment import android.os.Bundle import android.view.View import androidx.fragment.app.viewModels +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.R import nl.eduvpn.app.adapter.OrganizationAdapter @@ -117,8 +119,10 @@ class OrganizationSelectionFragment : BaseFragment when (parentAction) { diff --git a/app/src/main/java/nl/eduvpn/app/service/BackendService.kt b/app/src/main/java/nl/eduvpn/app/service/BackendService.kt index d3fb1642..8a833ce9 100644 --- a/app/src/main/java/nl/eduvpn/app/service/BackendService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/BackendService.kt @@ -158,8 +158,8 @@ class BackendService( } @Throws(CommonException::class) - fun discoverOrganizations(): String { - val dataWithError = goBackend.discoverOrganizations() + fun discoverOrganizations(searchFilter: String): String { + val dataWithError = goBackend.discoverOrganizations(searchFilter) if (dataWithError.isError) { throw CommonException(dataWithError.error) } @@ -170,8 +170,8 @@ class BackendService( } @Throws(CommonException::class) - fun discoverServers(): String { - val dataWithError = goBackend.discoverServers() + fun discoverServers(searchFilter: String): String { + val dataWithError = goBackend.discoverServers(searchFilter) if (dataWithError.isError) { throw CommonException(dataWithError.error) } diff --git a/app/src/main/java/nl/eduvpn/app/service/HistoryService.java b/app/src/main/java/nl/eduvpn/app/service/HistoryService.java index de85ec56..9230f78d 100644 --- a/app/src/main/java/nl/eduvpn/app/service/HistoryService.java +++ b/app/src/main/java/nl/eduvpn/app/service/HistoryService.java @@ -48,8 +48,6 @@ public class HistoryService { private List _listeners = new LinkedList<>(); - private @Nullable OrganizationList _memoryCachedOrganizationList = null; - /** * Constructor. */ @@ -101,18 +99,11 @@ public CurrentServer getCurrentServer() { } } - public @Nullable OrganizationList getOrganizationList() { - return _memoryCachedOrganizationList; - } public boolean hasSecureInternetServer() { return _addedServers.getSecureInternetServer() != null; } - public void setOrganizationList(@Nullable OrganizationList organizationList) { - _memoryCachedOrganizationList = organizationList; - } - /** * Removes all saved data for an instance. * diff --git a/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt b/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt index a48d7599..de0ae728 100644 --- a/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt @@ -32,14 +32,13 @@ class OrganizationService( ) { - suspend fun fetchServerList() : ServerList = withContext(Dispatchers.IO) { - val serverListString = backendService.discoverServers() + suspend fun fetchServerList(searchFilter: String) : ServerList = withContext(Dispatchers.IO) { + val serverListString = backendService.discoverServers(searchFilter) serializerService.deserializeServerList(serverListString) } - suspend fun fetchOrganizations(): OrganizationList = withContext(Dispatchers.IO) { - val organizationListString = backendService.discoverOrganizations() - val organizationListJson = JSONObject(organizationListString) - serializerService.deserializeOrganizationList(organizationListJson) + suspend fun fetchOrganizations(searchFilter: String): OrganizationList = withContext(Dispatchers.IO) { + val organizationListString = backendService.discoverOrganizations(searchFilter) + serializerService.deserializeOrganizationList(organizationListString) } } diff --git a/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt b/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt index b037cb10..5a25d784 100644 --- a/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt @@ -70,9 +70,6 @@ class PreferencesService( const val KEY_INSTANCE_LIST_INSTITUTE_ACCESS = KEY_INSTANCE_LIST_PREFIX + "institute_access" - const val KEY_SERVER_LIST_DATA = "server_list_data" - const val KEY_SERVER_LIST_TIMESTAMP = "server_list_timestamp" - const val KEY_SAVED_KEY_PAIRS = "saved_key_pairs" const val KEY_PREFERRED_COUNTRY = "preferred_country" @@ -263,55 +260,6 @@ class PreferencesService( } } - /** - * Returns the server list if it is recent (see constants for exact TTL). - * - * @return The server list if it is recent, otherwise null. - */ - fun getServerList(): ServerList? { - val timestamp = getSharedPreferences().getLong(KEY_SERVER_LIST_TIMESTAMP, 0L) - return if (System.currentTimeMillis() - timestamp < Constants.SERVER_LIST_VALID_FOR_MS) { - val serializedServerList = - getSharedPreferences().getString(KEY_SERVER_LIST_DATA, null) - ?: return null - try { - _serializerService.deserializeServerList(serializedServerList) - } catch (ex: Exception) { - Log.w(TAG, "Unable to parse server list!", ex) - null - } - } else { - getSharedPreferences().edit() - .remove(KEY_SERVER_LIST_DATA) - .remove(KEY_SERVER_LIST_TIMESTAMP) - .apply() - null - } - } - - /** - * Caches the server list. Only valid for a set amount, see constants for the exact TTL. - * - * @param serverList The server list to cache. Use null to remove previously set values. - */ - fun setServerList(serverList: ServerList?) { - if (serverList == null) { - getSharedPreferences().edit().remove(KEY_SERVER_LIST_DATA) - .remove(KEY_SERVER_LIST_TIMESTAMP) - .apply() - } else { - try { - val serializedServerList = _serializerService.serializeServerList(serverList) - getSharedPreferences().edit() - .putString(KEY_SERVER_LIST_DATA, serializedServerList) - .putLong(KEY_SERVER_LIST_TIMESTAMP, System.currentTimeMillis()) - .apply() - } catch (ex: Exception) { - Log.w(TAG, "Unable to set server list!") - } - } - } - fun setCurrentProtocol(protocol: Int) { getSharedPreferences().edit() .putInt(KEY_VPN_PROTOCOL, protocol) diff --git a/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt b/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt index 2eb87fc4..559f91d2 100644 --- a/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt @@ -132,180 +132,38 @@ class SerializerService { } } - /** - * Serializes an organization into a JSON object. - * - * @param organization The organization to serialize - * @return The organization as a JSON object. - * @throws UnknownFormatException Thrown if there was an error while serializing. - */ - @Throws(UnknownFormatException::class) - fun serializeOrganization(organization: Organization): JSONObject { - val result = JSONObject() - try { - result.put("org_id", organization.orgId) - if (organization.displayName.translations.isEmpty()) { - result.put("display_name", null) - } else { - val translations = JSONObject() - for ((key, value) in organization.displayName.translations) { - translations.put(key, value) - } - result.put("display_name", translations) - } - if (organization.keywordList.translations.isEmpty()) { - result.put("keyword_list", null) - } else { - val translations = JSONObject() - for ((key, value) in organization.keywordList.translations) { - translations.put(key, value) - } - result.put("keyword_list", translations) - } - result.put("secure_internet_home", organization.secureInternetHome) - } catch (ex: JSONException) { - throw UnknownFormatException(ex) - } - return result - } - - /** - * Deserializes an organization from JSON. - * - * @param jsonObject The JSON object to deserialize. - * @return The organization instance. - * @throws UnknownFormatException Thrown if the JSON has an unknown format. - */ - @Throws(UnknownFormatException::class) - fun deserializeOrganization(jsonObject: JSONObject): Organization { - return try { - val displayName: TranslatableString - displayName = if (jsonObject.isNull("display_name")) { - TranslatableString() - } else if (jsonObject["display_name"] is String) { - TranslatableString(jsonObject.getString("display_name")) - } else { - _parseAllTranslations(jsonObject.getJSONObject("display_name")) - } - val orgId = jsonObject.getString("org_id") - val translatableString: TranslatableString = - if (jsonObject.has("keyword_list") && !jsonObject.isNull("keyword_list")) { - if (jsonObject["keyword_list"] is JSONObject) { - _parseAllTranslations(jsonObject.getJSONObject("keyword_list")) - } else if (jsonObject["keyword_list"] is String) { - TranslatableString(jsonObject.getString("keyword_list")) - } else { - throw JSONException("keyword_list should be object or string") - } - } else { - TranslatableString() - } - var secureInternetHome: String? = null - if (jsonObject.has("secure_internet_home") && !jsonObject.isNull("secure_internet_home")) { - secureInternetHome = jsonObject.getString("secure_internet_home") - } - Organization(orgId, displayName, translatableString, secureInternetHome) - } catch (ex: JSONException) { - throw UnknownFormatException(ex) - } - } - /** * Deserializes a list of organizations. * - * @param jsonObject The json to deserialize from. - * @return The list of organizations created from the JSON. - * @throws UnknownFormatException Thrown if there was an error while deserializing. - */ - @Throws(UnknownFormatException::class) - fun deserializeOrganizationList(jsonObject: JSONObject): OrganizationList { - return try { - val result: MutableList = ArrayList() - val itemsList = jsonObject.getJSONArray("organization_list") - for (i in 0 until itemsList.length()) { - val serializedItem = itemsList.getJSONObject(i) - val organization = deserializeOrganization(serializedItem) - result.add(organization) - } - OrganizationList(result) - } catch (ex: JSONException) { - throw UnknownFormatException(ex) - } - } - - /** - * Serializes a list of organizations into a JSON format. - * - * @param organizationList The list of organizations to serialize. - * @return The messages as a JSON object. - * @throws UnknownFormatException Thrown if there was an error constructing the JSON. - */ - @Throws(UnknownFormatException::class) - fun serializeOrganizationList(organizationList: OrganizationList): JSONObject { - return try { - val result = JSONObject() - val organizationsArray = JSONArray() - for (organization in organizationList.organizationList) { - organizationsArray.put(serializeOrganization(organization)) - } - result.put("organization_list", organizationsArray) - result - } catch (ex: JSONException) { - throw UnknownFormatException(ex) - } - } - - /** - * Deserializes a list of organization servers. - * * @param json The json to deserialize from. * @return The list of organizations servers created from the JSON. * @throws UnknownFormatException Thrown if there was an error while deserializing. */ @Throws(UnknownFormatException::class) - fun deserializeServerList(json: String?): ServerList { + fun deserializeOrganizationList(json: String?): OrganizationList { return try { - jsonSerializer.decodeFromString(ServerList.serializer(), json!!) + jsonSerializer.decodeFromString(OrganizationList.serializer(), json!!) } catch (ex: SerializationException) { throw UnknownFormatException(ex) } } /** - * Serializes the server list to JSON format + * Deserializes a list of secure internet / institute access servers. * - * @param serverList The server list to serialize. - * @return The server list as a JSON object. + * @param json The json to deserialize from. + * @return The list of servers created from the JSON. * @throws UnknownFormatException Thrown if there was an error while deserializing. */ @Throws(UnknownFormatException::class) - fun serializeServerList(serverList: ServerList): String { + fun deserializeServerList(json: String?): ServerList { return try { - jsonSerializer.encodeToString(ServerList.serializer(), serverList) + jsonSerializer.decodeFromString(ServerList.serializer(), json!!) } catch (ex: SerializationException) { throw UnknownFormatException(ex) } } - /** - * Retrieves the translations from a JSON object. - * - * @param translationsObject The JSON object to retrieve the translations from. - * @return A TranslatableString instance. - * @throws JSONException Thrown if the input is in an unexpected format. - */ - @Throws(JSONException::class) - private fun _parseAllTranslations(translationsObject: JSONObject): TranslatableString { - val keysIterator = translationsObject.keys() - val translationsMap: MutableMap = HashMap() - while (keysIterator.hasNext()) { - val key = keysIterator.next() - val value = translationsObject.getString(key) - translationsMap[key] = value - } - return TranslatableString(translationsMap) - } - @Throws(UnknownFormatException::class) fun deserializeCookieAndStringData(json: String?): CookieAndStringData { return try { diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt index 1c291dfd..19d9ad1e 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/OrganizationSelectionViewModel.kt @@ -20,38 +20,27 @@ package nl.eduvpn.app.viewmodel import android.content.Context import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.map import androidx.lifecycle.switchMap -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import nl.eduvpn.app.R import nl.eduvpn.app.adapter.OrganizationAdapter import nl.eduvpn.app.entity.AuthorizationType import nl.eduvpn.app.entity.Instance -import nl.eduvpn.app.entity.Organization -import nl.eduvpn.app.entity.OrganizationList -import nl.eduvpn.app.entity.ServerList -import nl.eduvpn.app.entity.TranslatableString import nl.eduvpn.app.service.BackendService -import nl.eduvpn.app.service.EduVPNOpenVPNService import nl.eduvpn.app.service.HistoryService import nl.eduvpn.app.service.OrganizationService import nl.eduvpn.app.service.PreferencesService -import nl.eduvpn.app.service.SerializerService import nl.eduvpn.app.service.VPNConnectionService -import nl.eduvpn.app.utils.Log -import nl.eduvpn.app.utils.runCatchingCoroutine -import java.text.Collator -import java.util.Locale import javax.inject.Inject class OrganizationSelectionViewModel @Inject constructor( organizationService: OrganizationService, - private val preferencesService: PreferencesService, + preferencesService: PreferencesService, context: Context, backendService: BackendService, historyService: HistoryService, @@ -63,151 +52,99 @@ class OrganizationSelectionViewModel @Inject constructor( preferencesService, vpnConnectionService, ) { - private val organizations = MutableLiveData>() - private val instituteAccessServers = - MutableLiveData>() - private val secureInternetServers = - MutableLiveData>() val artworkVisible = MutableLiveData(true) - val searchText = MutableLiveData("") + val searchText = MutableStateFlow("") - init { - viewModelScope.launch(Dispatchers.IO) { - // We want to be able to handle async failures, so use supervisorScope - // https://kotlinlang.org/docs/reference/coroutines/exception-handling.html#supervision - supervisorScope { - val organizationListDeferred = if (!historyService.hasSecureInternetServer()) { - connectionState.postValue(ConnectionState.FetchingOrganizations) - async { - val organizationList = organizationService.fetchOrganizations() - historyService.organizationList = organizationList - organizationList - } - } else { - // We can't show any organization servers (secure internet), user needs to reset to switch. - connectionState.postValue(ConnectionState.FetchingServerList) - CompletableDeferred(OrganizationList(emptyList())) - } - val cachedServerList = preferencesService.getServerList() - val serverListDeferred = if (cachedServerList != null) { - CompletableDeferred(cachedServerList) - } else { - async { organizationService.fetchServerList() } - } - - val organizationList = - runCatchingCoroutine { organizationListDeferred.await() }.getOrElse { - Log.w(TAG, "Organizations call has failed!", it) - OrganizationList(emptyList()) - } - - val serverList = runCatchingCoroutine { serverListDeferred.await() }.getOrElse { - Log.w(TAG, "Server list call has failed!", it) - ServerList(emptyList()) - } + private val serverList: Flow> = searchText.map { filter -> + return@map organizationService.fetchServerList(filter).serverList ?: emptyList() + } - val sortedOrganizations = organizationList.organizationList.sortedWith( - Comparator.comparing( - { i -> i.displayName.bestTranslation }, - Collator.getInstance(Locale.getDefault()) + private val secureInternetServers = serverList.map { serverList -> + if (historyService.hasSecureInternetServer()) { + val result: MutableList = serverList.filter { it.authorizationType == AuthorizationType.Distributed } + .map { + OrganizationAdapter.OrganizationAdapterItem.SecureInternet(it) + }.toMutableList() + if (result.isNotEmpty()) { + result.add( + 0, OrganizationAdapter.OrganizationAdapterItem.Header( + R.drawable.ic_secure_internet, + R.string.header_secure_internet ) ) - - val sortedInstituteAccessServers = serverList.serverList.filter { - it.authorizationType == AuthorizationType.Local - }.sortedWith( - Comparator.comparing( - { i -> i.displayName.bestTranslation }, - Collator.getInstance(Locale.getDefault()) - ) - ).map { OrganizationAdapter.OrganizationAdapterItem.InstituteAccess(it) } - - val secureInternetServerList = serverList.serverList.filter { - it.authorizationType == AuthorizationType.Distributed - } - - organizations.postValue(sortedOrganizations) - instituteAccessServers.postValue(sortedInstituteAccessServers) - secureInternetServers.postValue(secureInternetServerList) - connectionState.postValue(ConnectionState.Ready) } + result + } else { + emptyList() } } - private fun matchesServer( - searchText: String, - displayName: TranslatableString, - keywords: TranslatableString? - ): Boolean { - return searchText.isBlank() || displayName.translations.any { keyValue -> - keyValue.value.contains(searchText, ignoreCase = true) - } || (keywords != null && keywords.translations.any { keyValue -> - keyValue.value.contains(searchText, ignoreCase = true) - }) + private val instituteAccessServers = serverList.map { serverList -> + val result: MutableList = serverList.filter { it.authorizationType == AuthorizationType.Local } + .map { + OrganizationAdapter.OrganizationAdapterItem.InstituteAccess(it) + }.toMutableList() + if (result.isNotEmpty()) { + result.add( + 0, OrganizationAdapter.OrganizationAdapterItem.Header( + R.drawable.ic_institute, + R.string.header_institute_access + ) + ) + } + result } - val adapterItems = organizations.switchMap { organizations -> - instituteAccessServers.switchMap { instituteAccessServers -> - secureInternetServers.switchMap { secureInternetServers -> - searchText.map { searchText -> - val resultList = mutableListOf() - // Search contains at least two dots - if (searchText.count { ".".contains(it) } > 1) { - resultList += OrganizationAdapter.OrganizationAdapterItem.Header( - R.drawable.ic_server, - R.string.header_connect_your_own_server - ) - resultList += OrganizationAdapter.OrganizationAdapterItem.AddServer( - searchText - ) - return@map resultList - } - - val instituteAccessServersFiltered = instituteAccessServers.filter { - matchesServer(searchText, it.server.displayName, it.server.keywords) - } - val secureInternetServersFiltered = if (historyService.hasSecureInternetServer()) { - secureInternetServers.filter { - matchesServer(searchText, it.displayName, it.keywords) - }.map { - OrganizationAdapter.OrganizationAdapterItem.SecureInternet(it) - } - } else { - organizations.filter { - matchesServer(searchText, it.displayName, it.keywordList) - }.map { - OrganizationAdapter.OrganizationAdapterItem.Organization(it) - } - } - if (instituteAccessServersFiltered.isNotEmpty()) { - resultList += OrganizationAdapter.OrganizationAdapterItem.Header( - R.drawable.ic_institute, - R.string.header_institute_access - ) - resultList += instituteAccessServersFiltered - } - if (secureInternetServersFiltered.isNotEmpty()) { - resultList += OrganizationAdapter.OrganizationAdapterItem.Header( - R.drawable.ic_secure_internet, - R.string.header_secure_internet - ) - resultList += secureInternetServersFiltered - } - resultList - } + private val organizations: Flow> = searchText.map { filter -> + if (historyService.hasSecureInternetServer()) { + emptyList() + } else { + val result: MutableList = organizationService.fetchOrganizations(filter) + .organizationList + ?.map { + OrganizationAdapter.OrganizationAdapterItem.Organization(it) + }?.toMutableList() ?: mutableListOf() + if (result.isNotEmpty()) { + result.add( + 0, OrganizationAdapter.OrganizationAdapterItem.Header( + R.drawable.ic_secure_internet, + R.string.header_secure_internet + ) + ) } + result } } - val noItemsFound = connectionState.switchMap { state -> - adapterItems.map { items -> - items.isEmpty() && state == ConnectionState.Ready + private val addServerItem = searchText.map { filter -> + // Search term contains at least two dots + if (filter.count { ".".contains(it) } > 1) { + val resultList = mutableListOf() + resultList += OrganizationAdapter.OrganizationAdapterItem.Header( + R.drawable.ic_server, + R.string.header_connect_your_own_server + ) + resultList += OrganizationAdapter.OrganizationAdapterItem.AddServer(filter) + resultList + } else { + emptyList() } } - companion object { - private val TAG = OrganizationSelectionViewModel::class.java.name + val adapterItems = combine( + addServerItem, + instituteAccessServers, + organizations, + secureInternetServers + ) { addServerItem, instituteAccessServers, organizations, secureInternetServers -> + addServerItem + instituteAccessServers + organizations + secureInternetServers + } + + val noItemsFound = connectionState.switchMap { state -> + adapterItems.asLiveData().map { items -> + items.isEmpty() && state == ConnectionState.Ready + } } } diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt index 8e488d7a..d813bea4 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/ServerSelectionViewModel.kt @@ -59,14 +59,8 @@ class ServerSelectionViewModel @Inject constructor( val connectingTo = MutableLiveData() - // We avoid refreshing the organization too frequently. - private val serverListCache = MutableLiveData>() - init { historyService.addListener(this) - preferencesService.getServerList()?.let { serverList -> - serverListCache.value = Pair(System.currentTimeMillis(), serverList) - } } override fun onCleared() { @@ -94,7 +88,7 @@ class ServerSelectionViewModel @Inject constructor( historyService.load() historyService.addListener(this) val needsServerList = historyService.addedServers?.secureInternetServer != null - if (needsServerList && (serverListCache.value == null || System.currentTimeMillis() - serverListCache.value!!.first > SERVER_LIST_CACHE_TTL)) { + if (needsServerList) { refreshServerList() } else { refreshInstances() @@ -110,11 +104,9 @@ class ServerSelectionViewModel @Inject constructor( Log.v(TAG, "Fetching server list...") viewModelScope.launch(Dispatchers.IO) { runCatchingCoroutine { - organizationService.fetchServerList() + organizationService.fetchServerList("") }.onSuccess { serverList -> Log.v(TAG, "Updated server list with latest entries.") - serverListCache.postValue(Pair(System.currentTimeMillis(), serverList)) - preferencesService.setServerList(serverList) refreshInstances() }.onFailure { throwable -> Log.w(TAG, "Unable to fetch server list. Trying to show servers without it.", throwable) diff --git a/common/src/main/cpp/jni.cpp b/common/src/main/cpp/jni.cpp index ca714357..bdd0ec7e 100644 --- a/common/src/main/cpp/jni.cpp +++ b/common/src/main/cpp/jni.cpp @@ -201,17 +201,17 @@ Java_org_eduvpn_common_GoBackend_register(JNIEnv *env, jobject /* this */, jstri } extern "C" JNIEXPORT jobject JNICALL -Java_org_eduvpn_common_GoBackend_discoverOrganizations(JNIEnv *env, jobject /* this */) { +Java_org_eduvpn_common_GoBackend_discoverOrganizations(JNIEnv *env, jobject /* this */, jstring search) { uintptr_t cookie = CookieNew(); - const char *searchString = ""; + const char *searchString = env->GetStringUTFChars(search, nullptr); DiscoOrganizations_return organizationsReturn = DiscoOrganizations(cookie, (char *)searchString); return CreateDataErrorTuple(env, organizationsReturn.r0, organizationsReturn.r1); } extern "C" JNIEXPORT jobject JNICALL -Java_org_eduvpn_common_GoBackend_discoverServers(JNIEnv *env, jobject /* this */) { +Java_org_eduvpn_common_GoBackend_discoverServers(JNIEnv *env, jobject /* this */, jstring search) { uintptr_t cookie = CookieNew(); - const char *searchString = ""; + const char *searchString = env->GetStringUTFChars(search, nullptr); DiscoServers_return serversReturn = DiscoServers(cookie, (char *)searchString); CookieDelete(cookie); return CreateDataErrorTuple(env, serversReturn.r0, serversReturn.r1); diff --git a/common/src/main/java/org/eduvpn/common/GoBackend.java b/common/src/main/java/org/eduvpn/common/GoBackend.java index 9697cc0e..c9615b56 100644 --- a/common/src/main/java/org/eduvpn/common/GoBackend.java +++ b/common/src/main/java/org/eduvpn/common/GoBackend.java @@ -28,8 +28,8 @@ public interface Callback { @Nullable String configDirectory, boolean debug ); - public native DataErrorTuple discoverOrganizations(); - public native DataErrorTuple discoverServers(); + public native DataErrorTuple discoverOrganizations(@NonNull String search); + public native DataErrorTuple discoverServers(@NonNull String search); public native DataErrorTuple getAddedServers(); public native DataErrorTuple getProfiles(int serverType, @NonNull String id, boolean preferTcp, boolean isStartUp); public native @Nullable String addServer(int serverType, @NonNull String id); From 62c77d46cf210cbd10480af8f8c26a7a132f46a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Thu, 6 Jun 2024 11:31:54 +0200 Subject: [PATCH 4/8] Change AddServer parameter value to nullptr --- common/src/main/cpp/jni.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/cpp/jni.cpp b/common/src/main/cpp/jni.cpp index bdd0ec7e..9ac0a2c0 100644 --- a/common/src/main/cpp/jni.cpp +++ b/common/src/main/cpp/jni.cpp @@ -223,7 +223,7 @@ Java_org_eduvpn_common_GoBackend_addServer(JNIEnv *env, jobject /* this */, jint uintptr_t cookie = CookieNew(); const char *id_str = env->GetStringUTFChars(id, nullptr); SetState(1); // Change first to main state to make sure we are not in a previous state. - char *error = AddServer(cookie, (int)serverType, (char *)id_str, 0); + char *error = AddServer(cookie, (int)serverType, (char *)id_str, nullptr); CookieDelete(cookie); // Do not delete the cookie, because it might be reused later in the flow if (error != nullptr) { From f694f397c3608658057c4cac04b30750eb3ae127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Thu, 6 Jun 2024 12:03:28 +0200 Subject: [PATCH 5/8] Add call for cleanUp, fix a threading issue --- .../main/java/nl/eduvpn/app/service/BackendService.kt | 5 +++++ .../nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt | 3 +++ .../main/java/nl/eduvpn/app/viewmodel/MainViewModel.kt | 5 ++++- common/src/main/cpp/jni.cpp | 9 +++++++++ common/src/main/java/org/eduvpn/common/GoBackend.java | 1 + 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nl/eduvpn/app/service/BackendService.kt b/app/src/main/java/nl/eduvpn/app/service/BackendService.kt index 8a833ce9..87176545 100644 --- a/app/src/main/java/nl/eduvpn/app/service/BackendService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/BackendService.kt @@ -338,6 +338,11 @@ class BackendService( goBackend.notifyDisconnected() } + suspend fun cleanUp() = withContext(Dispatchers.IO) { + val result = goBackend.cleanUp() + Log.i(TAG, "Cleaned up common VPN connection with message: $result") + } + fun getLogFile() : File? { val configDirectory = File(context.cacheDir, DIRECTORY_BACKEND_CONFIG_FILES) val configFile = File(configDirectory, "log") diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt index b5511bab..6238a872 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt @@ -229,6 +229,9 @@ class ConnectionStatusViewModel @Inject constructor( VPNService.VPNStatus.DISCONNECTED, VPNService.VPNStatus.FAILED -> { backendService.notifyDisconnecting() backendService.notifyDisconnected() + viewModelScope.launch { + backendService.cleanUp() + } } } } diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/MainViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/MainViewModel.kt index 2f61742e..85a19578 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/MainViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/MainViewModel.kt @@ -10,6 +10,7 @@ import com.wireguard.config.Config import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import nl.eduvpn.app.MainActivity import nl.eduvpn.app.R import nl.eduvpn.app.entity.AddedServer @@ -201,7 +202,9 @@ class MainViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { try { backendService.selectCountry(cookie, organizationId, countryCode) - historyService.load() + withContext(Dispatchers.Main) { + historyService.load() + } } catch (ex: Exception) { _mainParentAction.postValue(MainParentAction.ShowError(ex)) } diff --git a/common/src/main/cpp/jni.cpp b/common/src/main/cpp/jni.cpp index 9ac0a2c0..b2826e03 100644 --- a/common/src/main/cpp/jni.cpp +++ b/common/src/main/cpp/jni.cpp @@ -349,6 +349,15 @@ Java_org_eduvpn_common_GoBackend_notifyDisconnected(JNIEnv *env, jobject /* this SetState(11); } +extern "C" +JNIEXPORT jstring JNICALL +Java_org_eduvpn_common_GoBackend_cleanUp(JNIEnv *env, jobject /* this */) { + uintptr_t cookie = CookieNew(); + char *result = Cleanup(cookie); + CookieDelete(cookie); + return NativeStringToJString(env, result); +} + extern "C" JNIEXPORT jstring JNICALL Java_org_eduvpn_common_GoBackend_startProxyGuard(JNIEnv *env, jobject /* this */, jint sourcePort, jstring listen, jstring peer) { diff --git a/common/src/main/java/org/eduvpn/common/GoBackend.java b/common/src/main/java/org/eduvpn/common/GoBackend.java index c9615b56..b6f1ef38 100644 --- a/common/src/main/java/org/eduvpn/common/GoBackend.java +++ b/common/src/main/java/org/eduvpn/common/GoBackend.java @@ -48,5 +48,6 @@ public interface Callback { public native void notifyConnected(); public native void notifyDisconnecting(); public native void notifyDisconnected(); + public native @Nullable String cleanUp(); public native @Nullable String startProxyGuard(int sourcePort, @NotNull String listen, @NotNull String peer); } \ No newline at end of file From d8b6509ab98badf2f2e9a8bb328c582ecb0d5a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Thu, 6 Jun 2024 13:04:59 +0200 Subject: [PATCH 6/8] Add Cleanup call, fix some issues with OpenVPN status updates Adds the cleanup call, and fixes some incorrect 'disconnect' status notifications in the UI and backend. This was mostly due to the UI requesting a status update immediately from the ViewModel, which then triggered a status update with the disconnected state. Another issue was an incorrect VPN level to status mapping, where we incorrectly treated the 'no network' level as a disconnected state, while actually it was connecting. --- .../app/fragment/ConnectionStatusFragment.kt | 60 +++----------- .../app/service/EduVPNOpenVPNService.java | 2 +- .../viewmodel/ConnectionStatusViewModel.kt | 81 +++++++++++++++---- 3 files changed, 77 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt b/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt index 628c7658..9adb4ef7 100644 --- a/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt +++ b/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt @@ -59,14 +59,9 @@ import javax.inject.Inject */ class ConnectionStatusFragment : BaseFragment() { - 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 { viewModelFactory } @@ -96,14 +91,14 @@ class ConnectionStatusFragment : BaseFragment() 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() @@ -142,7 +137,7 @@ class ConnectionStatusFragment : BaseFragment() } } binding.renewSession.setOnClickListener { - disconnect() + viewModel.disconnect(activity) viewModel.renewSession() } viewModel.connectionParentAction.observe(viewLifecycleOwner) { parentAction -> @@ -154,7 +149,7 @@ class ConnectionStatusFragment : BaseFragment() 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() @@ -205,27 +200,23 @@ class ConnectionStatusFragment : BaseFragment() } } 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) @@ -233,13 +224,11 @@ class ConnectionStatusFragment : BaseFragment() // 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, @@ -247,13 +236,9 @@ class ConnectionStatusFragment : BaseFragment() ) 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) { @@ -263,7 +248,7 @@ class ConnectionStatusFragment : BaseFragment() } fun returnToHome() { - disconnect() + viewModel.disconnect(activity) val activity = activity as MainActivity? if (activity != null && !activity.isFinishing) { activity.setBackNavigationEnabled(false) @@ -304,31 +289,4 @@ class ConnectionStatusFragment : BaseFragment() } } } - - 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 - } } diff --git a/app/src/main/java/nl/eduvpn/app/service/EduVPNOpenVPNService.java b/app/src/main/java/nl/eduvpn/app/service/EduVPNOpenVPNService.java index ac429a56..f6627839 100644 --- a/app/src/main/java/nl/eduvpn/app/service/EduVPNOpenVPNService.java +++ b/app/src/main/java/nl/eduvpn/app/service/EduVPNOpenVPNService.java @@ -257,6 +257,7 @@ public static VPNStatus connectionStatusToVPNStatus(ConnectionStatus connectionS switch (connectionStatus) { case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: case LEVEL_CONNECTING_SERVER_REPLIED: + case LEVEL_NONETWORK: case LEVEL_START: return VPNStatus.CONNECTING; case LEVEL_CONNECTED: @@ -265,7 +266,6 @@ public static VPNStatus connectionStatusToVPNStatus(ConnectionStatus connectionS return VPNStatus.PAUSED; case LEVEL_AUTH_FAILED: return VPNStatus.FAILED; - case LEVEL_NONETWORK: case LEVEL_NOTCONNECTED: case LEVEL_WAITING_FOR_USER_INPUT: case UNKNOWN_LEVEL: diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt index 6238a872..421492c0 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt @@ -18,22 +18,28 @@ package nl.eduvpn.app.viewmodel +import android.app.Activity import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Handler +import android.os.Looper import android.text.Spanned import androidx.core.text.HtmlCompat import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import nl.eduvpn.app.CertExpiredBroadcastReceiver import nl.eduvpn.app.R import nl.eduvpn.app.entity.CertExpiryTimes import nl.eduvpn.app.entity.Profile +import nl.eduvpn.app.fragment.ConnectionStatusFragment import nl.eduvpn.app.service.* import nl.eduvpn.app.utils.Log import nl.eduvpn.app.utils.pendingIntentImmutableFlag @@ -76,7 +82,9 @@ class ConnectionStatusViewModel @Inject constructor( val serverProfiles = MutableLiveData>() val byteCountFlow = vpnService.byteCountFlow val ipFLow = vpnService.ipFlow + val protocol = vpnService.getProtocol() val canRenew = MutableLiveData(false) + val vpnStatus = MutableLiveData(VPNService.VPNStatus.DISCONNECTED) private val _connectionParentAction = MutableLiveData() val connectionParentAction = _connectionParentAction.toSingleEvent() @@ -86,6 +94,8 @@ class ConnectionStatusViewModel @Inject constructor( .setAction(CertExpiredBroadcastReceiver.ACTION) private val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, pendingIntentImmutableFlag) + private val gracefulDisconnectHandler = Handler(Looper.getMainLooper()) + init { val currentServer = historyService.currentServer @@ -127,6 +137,36 @@ class ConnectionStatusViewModel @Inject constructor( } serverProfiles.value = currentServer.getProfiles() profileName.value = currentServer.currentProfile?.displayName?.bestTranslation + viewModelScope.launch { + vpnService.asFlow().collect { status -> + val previousStatus = vpnStatus.value ?: VPNService.VPNStatus.DISCONNECTED + vpnStatus.postValue(status) + when (status) { + VPNService.VPNStatus.CONNECTED -> { + isInDisconnectMode.value = false + backendService.notifyConnected() + } + VPNService.VPNStatus.CONNECTING -> { + isInDisconnectMode.value = false + backendService.notifyConnecting() + } + VPNService.VPNStatus.PAUSED -> { + isInDisconnectMode.value = false + } + VPNService.VPNStatus.DISCONNECTED, VPNService.VPNStatus.FAILED -> { + gracefulDisconnectHandler.removeCallbacksAndMessages(null) + isInDisconnectMode.value = true + if (previousStatus != VPNService.VPNStatus.DISCONNECTED) { + backendService.notifyDisconnecting() + backendService.notifyDisconnected() + viewModelScope.launch { + backendService.cleanUp() + } + } + } + } + } + } } override fun onResume() { @@ -218,21 +258,34 @@ class ConnectionStatusViewModel @Inject constructor( return true } - fun notifyVpnStatus(vpnStatus: VPNService.VPNStatus) { - when (vpnStatus) { - VPNService.VPNStatus.CONNECTED -> { - backendService.notifyConnected() - } - VPNService.VPNStatus.CONNECTING, VPNService.VPNStatus.PAUSED -> { - backendService.notifyConnecting() - } - VPNService.VPNStatus.DISCONNECTED, VPNService.VPNStatus.FAILED -> { - backendService.notifyDisconnecting() - backendService.notifyDisconnected() - viewModelScope.launch { - backendService.cleanUp() + fun disconnect(activity: Activity?, retryCount: Int = 0) { + val isConnecting = vpnService.getStatus() == VPNService.VPNStatus.CONNECTING + 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(activity, retryCount + 1) + } else { + isInDisconnectMode.value = true + } + }, WAIT_FOR_DISCONNECT_UNTIL_MS.toLong()) } } + + fun getVpnErrorString(): String? { + return vpnService.getErrorString() + } + + companion object { + private const val WAIT_FOR_DISCONNECT_UNTIL_MS = 1_000 + private val TAG = ConnectionStatusFragment::class.java.name + } } From fce85c960acc346128db5b3bdcc5e6d347bc2345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Thu, 6 Jun 2024 13:25:17 +0200 Subject: [PATCH 7/8] Code cleanup --- .../app/service/PreferencesServiceTest.kt | 16 -------------- .../app/service/SerializerServiceTest.java | 22 ------------------- .../app/fragment/ConnectionStatusFragment.kt | 6 ----- .../nl/eduvpn/app/service/BackendService.kt | 7 ------ .../nl/eduvpn/app/service/HistoryService.java | 2 +- .../eduvpn/app/service/OrganizationService.kt | 1 - .../eduvpn/app/service/PreferencesService.kt | 5 ----- .../eduvpn/app/service/SerializerService.kt | 22 ------------------- .../app/service/VPNConnectionService.kt | 2 -- .../nl/eduvpn/app/service/WireGuardService.kt | 2 -- .../viewmodel/ConnectionStatusViewModel.kt | 2 -- 11 files changed, 1 insertion(+), 86 deletions(-) diff --git a/app/src/androidTest/java/nl/eduvpn/app/service/PreferencesServiceTest.kt b/app/src/androidTest/java/nl/eduvpn/app/service/PreferencesServiceTest.kt index d233bc37..cbae96f6 100644 --- a/app/src/androidTest/java/nl/eduvpn/app/service/PreferencesServiceTest.kt +++ b/app/src/androidTest/java/nl/eduvpn/app/service/PreferencesServiceTest.kt @@ -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) - } } diff --git a/app/src/androidTest/java/nl/eduvpn/app/service/SerializerServiceTest.java b/app/src/androidTest/java/nl/eduvpn/app/service/SerializerServiceTest.java index d545d397..ee9d925e 100644 --- a/app/src/androidTest/java/nl/eduvpn/app/service/SerializerServiceTest.java +++ b/app/src/androidTest/java/nl/eduvpn/app/service/SerializerServiceTest.java @@ -85,28 +85,6 @@ public void testInstanceSerialization() throws SerializerService.UnknownFormatEx assertEquals(instance.getSupportContact(), deserializedInstance.getSupportContact()); } - @Test - public void testOrganizationListSerialization() throws SerializerService.UnknownFormatException { - Map 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 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. * diff --git a/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt b/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt index 9adb4ef7..ea0f6ca6 100644 --- a/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt +++ b/app/src/main/java/nl/eduvpn/app/fragment/ConnectionStatusFragment.kt @@ -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 @@ -44,14 +41,11 @@ 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. diff --git a/app/src/main/java/nl/eduvpn/app/service/BackendService.kt b/app/src/main/java/nl/eduvpn/app/service/BackendService.kt index 87176545..47e5c863 100644 --- a/app/src/main/java/nl/eduvpn/app/service/BackendService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/BackendService.kt @@ -23,11 +23,7 @@ import org.eduvpn.common.GoBackend import org.eduvpn.common.GoBackend.Callback import org.eduvpn.common.ServerType import java.io.File -import java.io.FileDescriptor -import java.net.InetAddress import java.net.NetworkInterface -import java.util.Collections -import java.util.Locale class BackendService( @@ -52,8 +48,6 @@ class BackendService( private val goBackend = GoBackend() private var pendingOAuthCookie: Int? = null private var pendingProfileSelectionCookie: Int? = null - var lastSelectedProfile: String? = null - private set private var onConfigReady: ((SerializedVpnConfig, Boolean) -> Unit)? = null @@ -266,7 +260,6 @@ class BackendService( @kotlin.jvm.Throws(CommonException::class) suspend fun selectProfile(profile: Profile, preferTcp: Boolean) { - lastSelectedProfile = profile.profileId val cookie = pendingProfileSelectionCookie if (cookie != null) { val result = goBackend.selectProfile(cookie, profile.profileId) diff --git a/app/src/main/java/nl/eduvpn/app/service/HistoryService.java b/app/src/main/java/nl/eduvpn/app/service/HistoryService.java index 9230f78d..22939d78 100644 --- a/app/src/main/java/nl/eduvpn/app/service/HistoryService.java +++ b/app/src/main/java/nl/eduvpn/app/service/HistoryService.java @@ -46,7 +46,7 @@ public class HistoryService { private final BackendService _backendService; - private List _listeners = new LinkedList<>(); + private final List _listeners = new LinkedList<>(); /** * Constructor. diff --git a/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt b/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt index de0ae728..c2fc5619 100644 --- a/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/OrganizationService.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import nl.eduvpn.app.entity.OrganizationList import nl.eduvpn.app.entity.ServerList -import org.json.JSONObject /** * Service which provides the configurations for organization related data model. diff --git a/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt b/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt index 5a25d784..51f31a16 100644 --- a/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/PreferencesService.kt @@ -17,7 +17,6 @@ package nl.eduvpn.app.service - import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences @@ -35,7 +34,6 @@ import org.json.JSONObject import java.io.File import java.io.IOException - /** * This service is used to save temporary data * Created by Daniel Zolnai on 2016-10-11. @@ -70,9 +68,6 @@ class PreferencesService( const val KEY_INSTANCE_LIST_INSTITUTE_ACCESS = KEY_INSTANCE_LIST_PREFIX + "institute_access" - const val KEY_SAVED_KEY_PAIRS = "saved_key_pairs" - const val KEY_PREFERRED_COUNTRY = "preferred_country" - const val KEY_STORAGE_VERSION = "storage_version" } diff --git a/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt b/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt index 559f91d2..8cc8a663 100644 --- a/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/SerializerService.kt @@ -17,7 +17,6 @@ package nl.eduvpn.app.service import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json import nl.eduvpn.app.entity.AddedServers import nl.eduvpn.app.entity.CertExpiryTimes @@ -26,20 +25,12 @@ import nl.eduvpn.app.entity.CookieAndStringArrayData import nl.eduvpn.app.entity.CookieAndStringData import nl.eduvpn.app.entity.CurrentServer import nl.eduvpn.app.entity.Instance -import nl.eduvpn.app.entity.Organization import nl.eduvpn.app.entity.OrganizationList -import nl.eduvpn.app.entity.Profile import nl.eduvpn.app.entity.SerializedVpnConfig import nl.eduvpn.app.entity.ServerList import nl.eduvpn.app.entity.Settings -import nl.eduvpn.app.entity.TranslatableString -import org.json.JSONArray import org.json.JSONException import org.json.JSONObject -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.TimeZone /** * This service is responsible for (de)serializing objects used in the app. @@ -48,15 +39,6 @@ import java.util.TimeZone class SerializerService { class UnknownFormatException internal constructor(throwable: Throwable?) : Exception(throwable) - @Throws(UnknownFormatException::class) - fun deserializeProfileList(json: String?): List { - return try { - jsonSerializer.decodeFromString(ListSerializer(Profile.serializer()), json!!) - } catch (ex: SerializationException) { - throw UnknownFormatException(ex) - } - } - /** * Serializes an instance to a JSON format. * @@ -228,10 +210,6 @@ class SerializerService { } companion object { - private val API_DATE_FORMAT: DateFormat = - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply { - timeZone = TimeZone.getTimeZone("UTC") - } private val jsonSerializer: Json = Json { ignoreUnknownKeys = true coerceInputValues = true diff --git a/app/src/main/java/nl/eduvpn/app/service/VPNConnectionService.kt b/app/src/main/java/nl/eduvpn/app/service/VPNConnectionService.kt index b6d00bbf..e4361ccf 100644 --- a/app/src/main/java/nl/eduvpn/app/service/VPNConnectionService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/VPNConnectionService.kt @@ -9,7 +9,6 @@ import androidx.core.app.NotificationCompat import androidx.lifecycle.Observer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import nl.eduvpn.app.Constants import nl.eduvpn.app.DisconnectVPNBroadcastReceiver @@ -20,7 +19,6 @@ import nl.eduvpn.app.utils.FormattingUtils import nl.eduvpn.app.utils.Log import nl.eduvpn.app.utils.pendingIntentImmutableFlag import org.eduvpn.common.Protocol -import java.lang.reflect.InvocationTargetException class VPNConnectionService( private val preferencesService: PreferencesService, diff --git a/app/src/main/java/nl/eduvpn/app/service/WireGuardService.kt b/app/src/main/java/nl/eduvpn/app/service/WireGuardService.kt index 4743becc..548f0608 100644 --- a/app/src/main/java/nl/eduvpn/app/service/WireGuardService.kt +++ b/app/src/main/java/nl/eduvpn/app/service/WireGuardService.kt @@ -3,7 +3,6 @@ package nl.eduvpn.app.service import android.app.Activity import android.app.Notification import android.content.Context -import android.os.ParcelFileDescriptor import com.wireguard.android.backend.BackendException import com.wireguard.android.backend.GoBackend import com.wireguard.android.backend.Tunnel @@ -13,7 +12,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map diff --git a/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt b/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt index 421492c0..e4b3ce19 100644 --- a/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt +++ b/app/src/main/java/nl/eduvpn/app/viewmodel/ConnectionStatusViewModel.kt @@ -33,7 +33,6 @@ import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import nl.eduvpn.app.CertExpiredBroadcastReceiver import nl.eduvpn.app.R @@ -44,7 +43,6 @@ import nl.eduvpn.app.service.* import nl.eduvpn.app.utils.Log import nl.eduvpn.app.utils.pendingIntentImmutableFlag import nl.eduvpn.app.utils.toSingleEvent -import java.util.* import javax.inject.Inject import javax.inject.Named From b0067f7263373237bf5c0d27de88b777b936db39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Zolnai?= Date: Thu, 6 Jun 2024 14:47:20 +0200 Subject: [PATCH 8/8] Bump common to 2.0.1 --- common/libs/eduvpn-common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/libs/eduvpn-common b/common/libs/eduvpn-common index 3d47c630..e12a9820 160000 --- a/common/libs/eduvpn-common +++ b/common/libs/eduvpn-common @@ -1 +1 @@ -Subproject commit 3d47c63005ad962951080370a647914ab7c34e94 +Subproject commit e12a9820895b48de6d1b8408364fec45958bc6c5