From e6b040265d0f3955cab41fa943db695e423dd6cc Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 31 Jan 2025 08:27:55 +0100 Subject: [PATCH 01/14] android: Fix updating password for managed profiles Without data source set on the profile, this caused the app to crash with a null pointer dereference when it is updated. --- .../android/data/VpnProfileManagedDataSource.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java index 414d5bc4e..a76241b4e 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java @@ -98,7 +98,14 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource @Override public VpnProfile getVpnProfile(UUID uuid) { - return mManagedConfigurationService.getManagedProfiles().get(uuid.toString()); + final VpnProfile vpnProfile = mManagedConfigurationService.getManagedProfiles().get(uuid.toString()); + if (vpnProfile != null) + { + final String password = mSharedPreferences.getString(uuid.toString(), vpnProfile.getPassword()); + vpnProfile.setPassword(password); + vpnProfile.setDataSource(this); + } + return vpnProfile; } @Override From ff2010c8da7755c9209043b9e376194e5f1565f8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 3 Feb 2025 13:21:41 +0100 Subject: [PATCH 02/14] android: Update NDK version and enable support for 16KiB page tables --- src/frontends/android/app/build.gradle | 2 +- src/frontends/android/app/src/main/jni/Application.mk | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index cab3a6c6d..047ba8736 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -19,7 +19,7 @@ android { } } - ndkVersion "26.1.10909125" + ndkVersion "27.2.12479018" externalNativeBuild { ndkBuild { diff --git a/src/frontends/android/app/src/main/jni/Application.mk b/src/frontends/android/app/src/main/jni/Application.mk index 2133d20c6..a71cd74c9 100644 --- a/src/frontends/android/app/src/main/jni/Application.mk +++ b/src/frontends/android/app/src/main/jni/Application.mk @@ -1 +1,2 @@ APP_PLATFORM := android-21 +APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true From 87610799f2911a7e3515fdc1280da6a64c96b004 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 4 Feb 2025 14:07:37 +0100 Subject: [PATCH 03/14] android: Properly deinit library if parsing an IP fails This can happen with empty strings, which might be set for managed profiles, which caused the refcounting to be askew and the resolver not to work after connecting once because it was flushed and disabled. --- .../android/app/src/main/jni/libandroidbridge/charonservice.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index 89868a710..12ac01727 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -820,6 +820,7 @@ JNI_METHOD_P(org_strongswan_android_utils, Utils, parseInetAddressBytes, jbyteAr host = host_create_from_string(str, 0); if (!host) { + library_deinit(); free(str); return NULL; } From 4f808cb2b0cab9b022c50e0534bf95843e4c50d6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 4 Feb 2025 15:33:33 +0100 Subject: [PATCH 04/14] android: Allow setting the password in managed profiles To avoid complicated changes in the UI, users can still update it. But the default, if they clear the field, will be the managed password. --- .../android/data/ManagedVpnProfile.java | 1 + .../data/VpnProfileManagedDataSource.java | 40 +++++++++++-------- .../strings_managed_configuration.xml | 2 + .../strings_managed_configuration.xml | 2 + .../strings_managed_configuration.xml | 2 + .../strings_managed_configuration.xml | 2 + .../strings_managed_configuration.xml | 2 + .../strings_managed_configuration.xml | 2 + .../values/strings_managed_configuration.xml | 2 + .../main/res/xml/managed_configuration.xml | 7 ++++ 10 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java index 054dde19f..5dc7b7744 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java @@ -135,6 +135,7 @@ public class ManagedVpnProfile extends VpnProfile setLocalId(local.getString(VpnProfileDataSource.KEY_LOCAL_ID)); setUsername(local.getString(VpnProfileDataSource.KEY_USERNAME)); + setPassword(local.getString(VpnProfileDataSource.KEY_PASSWORD)); final String userCertificateData = local.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); final String userCertificatePassword = local.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE_PASSWORD, ""); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java index a76241b4e..0f6ec6292 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2025 Tobias Brunner * Copyright (C) 2023 Relution GmbH * * Copyright (C) secunet Security Networks AG @@ -75,17 +76,14 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource @Override public boolean updateVpnProfile(VpnProfile profile) { - final VpnProfile existingProfile = getVpnProfile(profile.getUUID()); - if (existingProfile == null) + final VpnProfile managedProfile = mManagedConfigurationService.getManagedProfiles().get(profile.getUUID().toString()); + if (managedProfile == null) { return false; } - final String password = profile.getPassword(); - existingProfile.setPassword(password); - final SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putString(profile.getUUID().toString(), password); + editor.putString(profile.getUUID().toString(), profile.getPassword()); return editor.commit(); } @@ -95,17 +93,28 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource return false; } + /** + * Clone and prepare the given managed profile before handing it out. + * @param managedProfile profile to prepare + */ + private VpnProfile prepareVpnProfile(VpnProfile managedProfile) + { + final String password = mSharedPreferences.getString(managedProfile.getUUID().toString(), managedProfile.getPassword()); + final VpnProfile vpnProfile = managedProfile.clone(); + vpnProfile.setPassword(password); + vpnProfile.setDataSource(this); + return vpnProfile; + } + @Override public VpnProfile getVpnProfile(UUID uuid) { - final VpnProfile vpnProfile = mManagedConfigurationService.getManagedProfiles().get(uuid.toString()); - if (vpnProfile != null) + final VpnProfile managedProfile = mManagedConfigurationService.getManagedProfiles().get(uuid.toString()); + if (managedProfile != null) { - final String password = mSharedPreferences.getString(uuid.toString(), vpnProfile.getPassword()); - vpnProfile.setPassword(password); - vpnProfile.setDataSource(this); + return prepareVpnProfile(managedProfile); } - return vpnProfile; + return null; } @Override @@ -113,12 +122,9 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource { final Map managedVpnProfiles = mManagedConfigurationService.getManagedProfiles(); final List vpnProfiles = new ArrayList<>(); - for (final VpnProfile vpnProfile : managedVpnProfiles.values()) + for (final VpnProfile managedProfile : managedVpnProfiles.values()) { - final String password = mSharedPreferences.getString(vpnProfile.getUUID().toString(), vpnProfile.getPassword()); - vpnProfile.setPassword(password); - vpnProfile.setDataSource(this); - vpnProfiles.add(vpnProfile); + vpnProfiles.add(prepareVpnProfile(managedProfile)); } return vpnProfiles; } diff --git a/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml index b64f5461c..74e59cfe7 100644 --- a/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml @@ -85,6 +85,8 @@ Specifies information about the client Identity/username for EAP authentication (Optional) If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password + Password for EAP authentication (Optional) + If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it and may store it locally @string/profile_local_id_label @string/profile_local_id_hint_user @string/profile_user_certificate_label diff --git a/src/frontends/android/app/src/main/res/xml/managed_configuration.xml b/src/frontends/android/app/src/main/res/xml/managed_configuration.xml index 105b3f839..e86ad721e 100644 --- a/src/frontends/android/app/src/main/res/xml/managed_configuration.xml +++ b/src/frontends/android/app/src/main/res/xml/managed_configuration.xml @@ -176,6 +176,13 @@ android:restrictionType="string" android:title="@string/managed_config_local_eap_id_title" /> + + Date: Tue, 4 Feb 2025 15:53:46 +0100 Subject: [PATCH 05/14] android: Ignore empty strings for settings in managed profiles Unspecified settings should be set to null, while some MDMs might send them as empty strings, which could cause issues (like an empty password or trying to parse an empty DNS server address). --- .../android/data/ManagedVpnProfile.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java index 5dc7b7744..30943aa6b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java @@ -68,9 +68,9 @@ public class ManagedVpnProfile extends VpnProfile setMTU(getInt(bundle, VpnProfileDataSource.KEY_MTU, Constants.MTU_MIN, Constants.MTU_MAX)); setNATKeepAlive(getInt(bundle, VpnProfileDataSource.KEY_NAT_KEEPALIVE, Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX)); - setIkeProposal(bundle.getString(VpnProfileDataSource.KEY_IKE_PROPOSAL)); - setEspProposal(bundle.getString(VpnProfileDataSource.KEY_ESP_PROPOSAL)); - setDnsServers(bundle.getString(VpnProfileDataSource.KEY_DNS_SERVERS)); + setIkeProposal(getString(bundle, VpnProfileDataSource.KEY_IKE_PROPOSAL)); + setEspProposal(getString(bundle, VpnProfileDataSource.KEY_ESP_PROPOSAL)); + setDnsServers(getString(bundle, VpnProfileDataSource.KEY_DNS_SERVERS)); flags = addPositiveFlag(flags, bundle, KEY_TRANSPORT_IPV6_FLAG, VpnProfile.FLAGS_IPv6_TRANSPORT); final Bundle splitTunneling = bundle.getBundle(VpnProfileDataSource.KEY_SPLIT_TUNNELING); @@ -79,8 +79,8 @@ public class ManagedVpnProfile extends VpnProfile splitFlags = addPositiveFlag(splitFlags, splitTunneling, KEY_SPLIT_TUNNELLING_BLOCK_IPV4_FLAG, VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4); splitFlags = addPositiveFlag(splitFlags, splitTunneling, KEY_SPLIT_TUNNELLING_BLOCK_IPV6_FLAG, VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6); - setExcludedSubnets(splitTunneling.getString(VpnProfileDataSource.KEY_EXCLUDED_SUBNETS)); - setIncludedSubnets(splitTunneling.getString(VpnProfileDataSource.KEY_INCLUDED_SUBNETS)); + setExcludedSubnets(getString(splitTunneling, VpnProfileDataSource.KEY_EXCLUDED_SUBNETS)); + setIncludedSubnets(getString(splitTunneling, VpnProfileDataSource.KEY_INCLUDED_SUBNETS)); } setSplitTunneling(splitFlags); @@ -110,7 +110,7 @@ public class ManagedVpnProfile extends VpnProfile setGateway(remote.getString(VpnProfileDataSource.KEY_GATEWAY)); setPort(getInt(remote, VpnProfileDataSource.KEY_PORT, 1, 65_535)); - setRemoteId(remote.getString(VpnProfileDataSource.KEY_REMOTE_ID)); + setRemoteId(getString(remote, VpnProfileDataSource.KEY_REMOTE_ID)); final String certificateData = remote.getString(VpnProfileDataSource.KEY_CERTIFICATE); if (!TextUtils.isEmpty(certificateData)) @@ -133,9 +133,9 @@ public class ManagedVpnProfile extends VpnProfile return flags; } - setLocalId(local.getString(VpnProfileDataSource.KEY_LOCAL_ID)); - setUsername(local.getString(VpnProfileDataSource.KEY_USERNAME)); - setPassword(local.getString(VpnProfileDataSource.KEY_PASSWORD)); + setLocalId(getString(local, VpnProfileDataSource.KEY_LOCAL_ID)); + setUsername(getString(local, VpnProfileDataSource.KEY_USERNAME)); + setPassword(getString(local, VpnProfileDataSource.KEY_PASSWORD)); final String userCertificateData = local.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); final String userCertificatePassword = local.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE_PASSWORD, ""); @@ -155,6 +155,12 @@ public class ManagedVpnProfile extends VpnProfile return value < min || value > max ? null : value; } + private static String getString(final Bundle bundle, final String key) + { + final String value = bundle.getString(key); + return TextUtils.isEmpty(value) ? null : value; + } + private static int addPositiveFlag(int flags, Bundle bundle, String key, int flag) { if (bundle.getBoolean(key)) From 9a92088bb498a9fd7e9d79295d8ae4040b4b2c66 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 5 Feb 2025 15:27:37 +0100 Subject: [PATCH 06/14] android: Replace deprecated fragment menu APIs --- .../ui/SelectedApplicationsListFragment.java | 15 +++++++++----- .../ui/TrustedCertificateListFragment.java | 16 +++++++++++---- .../android/ui/VpnProfileListFragment.java | 20 ++++++++++--------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java index 5ef78b295..89b581530 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java @@ -44,12 +44,13 @@ import java.util.TreeSet; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.SearchView; +import androidx.core.view.MenuProvider; import androidx.fragment.app.ListFragment; import androidx.loader.app.LoaderManager; import androidx.loader.content.AsyncTaskLoader; import androidx.loader.content.Loader; -public class SelectedApplicationsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks, List>>, SearchView.OnQueryTextListener +public class SelectedApplicationsListFragment extends ListFragment implements MenuProvider, LoaderManager.LoaderCallbacks, List>>, SearchView.OnQueryTextListener { private SelectedApplicationsAdapter mAdapter; private SortedSet mSelection; @@ -58,7 +59,7 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setHasOptionsMenu(true); + requireActivity().addMenuProvider(this, getViewLifecycleOwner()); final boolean readOnly = getActivity().getIntent().getBooleanExtra(VpnProfileDataSource.KEY_READ_ONLY, false); getListView().setChoiceMode(readOnly ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_MULTIPLE); @@ -134,17 +135,21 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { MenuItem item = menu.add(R.string.search); item.setIcon(android.R.drawable.ic_menu_search); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); + } - super.onCreateOptionsMenu(menu, inflater); + @Override + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) + { + return false; } @Override diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java index 1ceba87f7..940170943 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java @@ -44,13 +44,15 @@ import java.util.Map.Entry; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.MenuProvider; import androidx.fragment.app.ListFragment; +import androidx.lifecycle.Lifecycle; import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.content.AsyncTaskLoader; import androidx.loader.content.Loader; -public class TrustedCertificateListFragment extends ListFragment implements LoaderCallbacks>, OnQueryTextListener +public class TrustedCertificateListFragment extends ListFragment implements MenuProvider, LoaderCallbacks>, OnQueryTextListener { public static final String EXTRA_CERTIFICATE_SOURCE = "certificate_source"; private OnTrustedCertificateSelectedListener mListener; @@ -69,7 +71,7 @@ public class TrustedCertificateListFragment extends ListFragment implements Load public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setHasOptionsMenu(true); + requireActivity().addMenuProvider(this, getViewLifecycleOwner(), Lifecycle.State.RESUMED); setEmptyText(getString(R.string.no_certificates)); @@ -105,17 +107,23 @@ public class TrustedCertificateListFragment extends ListFragment implements Load } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { MenuItem item = menu.add(R.string.search); item.setIcon(android.R.drawable.ic_menu_search); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } + @Override + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) + { + return false; + } + @Override public boolean onQueryTextSubmit(String query) { /* already handled when the text changes */ diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java index 6a22fdf5b..9b25785f1 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java @@ -57,10 +57,13 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; +import androidx.annotation.NonNull; +import androidx.core.view.MenuProvider; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Lifecycle; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -public class VpnProfileListFragment extends Fragment +public class VpnProfileListFragment extends Fragment implements MenuProvider { private static final String SELECTED_KEY = "SELECTED"; @@ -148,6 +151,7 @@ public class VpnProfileListFragment extends Fragment if (!mReadOnly) { + requireActivity().addMenuProvider(this, getViewLifecycleOwner()); mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mListView.setMultiChoiceModeListener(mVpnProfileSelected); } @@ -167,8 +171,6 @@ public class VpnProfileListFragment extends Fragment if (!mReadOnly) { - setHasOptionsMenu(true); - ArrayList selected = null; if (savedInstanceState != null) { @@ -218,13 +220,13 @@ public class VpnProfileListFragment extends Fragment } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { - inflater.inflate(R.menu.profile_list, menu); + menuInflater.inflate(R.menu.profile_list, menu); } @Override - public void onPrepareOptionsMenu(Menu menu) + public void onPrepareMenu(@NonNull Menu menu) { final MenuItem addProfile = menu.findItem(R.id.add_profile); if (addProfile != null) @@ -236,16 +238,16 @@ public class VpnProfileListFragment extends Fragment } @Override - public boolean onOptionsItemSelected(MenuItem item) + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { - if (item.getItemId() == R.id.add_profile) + if (menuItem.getItemId() == R.id.add_profile) { Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); startActivity(connectionIntent); return true; } - return super.onOptionsItemSelected(item); + return false; } private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() From 26eef1f095e99bfdb9b4cf8c9974346fc1af2a07 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 5 Feb 2025 16:13:13 +0100 Subject: [PATCH 07/14] android: Replace deprecated onBackPressed() and enable predictive back gestures Doesn't really make a difference it seems. --- .../android/app/src/main/AndroidManifest.xml | 1 + .../ui/SelectedApplicationsActivity.java | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml index a5e6aabc2..51c6ff89e 100644 --- a/src/frontends/android/app/src/main/AndroidManifest.xml +++ b/src/frontends/android/app/src/main/AndroidManifest.xml @@ -37,6 +37,7 @@ android:label="@string/app_name" android:theme="@style/ApplicationTheme" android:networkSecurityConfig="@xml/network_security_config" + android:enableOnBackInvokedCallback="true" android:allowBackup="false" > Date: Wed, 5 Feb 2025 16:27:57 +0100 Subject: [PATCH 08/14] android: Suppress warning in implementation only used for Android < 33 --- .../java/org/strongswan/android/data/ManagedConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java index a429f5fc8..5709ff36d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java @@ -121,6 +121,7 @@ public class ManagedConfiguration return Arrays.asList(bundles); } + @SuppressWarnings("deprecation") @NonNull private static List getBundleArrayListCompat(final Bundle bundle, final String key) { From 4e2c88f7ed1f57eaf496398438ffd097f31e9d79 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 5 Feb 2025 16:35:39 +0100 Subject: [PATCH 09/14] android: Handle deprecated getParcelable* and getSerializable methods --- .../ui/RemediationInstructionFragment.java | 25 +++++++++++++++++-- .../ui/RemediationInstructionsActivity.java | 11 +++++++- .../ui/RemediationInstructionsFragment.java | 25 +++++++++++++++++-- .../ui/TrustedCertificateImportActivity.java | 16 +++++++++++- .../ui/TrustedCertificateListFragment.java | 16 +++++++++++- .../android/ui/VpnProfileImportActivity.java | 14 +++++++++++ 6 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java index ea33a14e6..c26741504 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java @@ -16,6 +16,7 @@ package org.strongswan.android.ui; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -58,7 +59,14 @@ public class RemediationInstructionFragment extends ListFragment if (savedInstanceState != null) { - mInstruction = savedInstanceState.getParcelable(ARG_REMEDIATION_INSTRUCTION); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + mInstruction = getInstructionCompat(savedInstanceState); + } + else + { + mInstruction = savedInstanceState.getParcelable(ARG_REMEDIATION_INSTRUCTION, RemediationInstruction.class); + } } /* show dividers only between list items */ getListView().setHeaderDividersEnabled(false); @@ -85,7 +93,14 @@ public class RemediationInstructionFragment extends ListFragment Bundle args = getArguments(); if (args != null) { - mInstruction = args.getParcelable(ARG_REMEDIATION_INSTRUCTION); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + mInstruction = getInstructionCompat(args); + } + else + { + mInstruction = args.getParcelable(ARG_REMEDIATION_INSTRUCTION, RemediationInstruction.class); + } } updateView(mInstruction); } @@ -117,4 +132,10 @@ public class RemediationInstructionFragment extends ListFragment setListAdapter(null); } } + + @SuppressWarnings("deprecation") + private static RemediationInstruction getInstructionCompat(Bundle bundle) + { + return bundle.getParcelable(ARG_REMEDIATION_INSTRUCTION); + } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java index 4d8f2f1a3..d1de55214 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java @@ -43,7 +43,16 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement if (frag != null) { /* two-pane layout, update fragment */ Bundle extras = getIntent().getExtras(); - ArrayList list = extras.getParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS); + ArrayList list = null; + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) + { + list = RemediationInstructionsFragment.getInstructionsCompat(extras); + } + else + { + list = extras.getParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, + RemediationInstruction.class); + } frag.updateView(list); } else diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java index 0f74bfd41..937f32c0d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java @@ -17,6 +17,7 @@ package org.strongswan.android.ui; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.ListView; @@ -55,7 +56,14 @@ public class RemediationInstructionsFragment extends ListFragment if (savedInstanceState != null) { - mInstructions = savedInstanceState.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + mInstructions = getInstructionsCompat(savedInstanceState); + } + else + { + mInstructions = savedInstanceState.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS, RemediationInstruction.class); + } mCurrentPosition = savedInstanceState.getInt(KEY_POSITION); } } @@ -93,7 +101,14 @@ public class RemediationInstructionsFragment extends ListFragment Bundle args = getArguments(); if (mInstructions == null && args != null) { - mInstructions = args.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + mInstructions = getInstructionsCompat(args); + } + else + { + mInstructions = args.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS, RemediationInstruction.class); + } } updateView(mInstructions); @@ -123,4 +138,10 @@ public class RemediationInstructionsFragment extends ListFragment mInstructions = instructions; mAdapter.setData(mInstructions); } + + @SuppressWarnings("deprecation") + public static ArrayList getInstructionsCompat(Bundle bundle) + { + return bundle.getParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS); + } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java index 60e57b03b..322e21e2c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -21,6 +21,7 @@ import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.widget.Toast; @@ -191,7 +192,14 @@ public class TrustedCertificateImportActivity extends AppCompatActivity { final X509Certificate certificate; - certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + certificate = getCertificateCompat(getArguments()); + } + else + { + certificate = getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE, X509Certificate.class); + } return new AlertDialog.Builder(getActivity()) .setIcon(R.mipmap.ic_app) @@ -230,5 +238,11 @@ public class TrustedCertificateImportActivity extends AppCompatActivity { getActivity().finish(); } + + @SuppressWarnings("deprecation") + private static X509Certificate getCertificateCompat(Bundle bundle) + { + return (X509Certificate)bundle.getSerializable(VpnProfileDataSource.KEY_CERTIFICATE); + } } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java index 940170943..19f38f6bb 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java @@ -17,6 +17,7 @@ package org.strongswan.android.ui; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import android.view.Menu; @@ -83,7 +84,14 @@ public class TrustedCertificateListFragment extends ListFragment implements Menu Bundle arguments = getArguments(); if (arguments != null) { - mSource = (TrustedCertificateSource)arguments.getSerializable(EXTRA_CERTIFICATE_SOURCE); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + mSource = getCertificateSourceCompat(arguments); + } + else + { + mSource = arguments.getSerializable(EXTRA_CERTIFICATE_SOURCE, TrustedCertificateSource.class); + } } LoaderManager.getInstance(this).initLoader(0, null, this); @@ -268,4 +276,10 @@ public class TrustedCertificateListFragment extends ListFragment implements Menu } } } + + @SuppressWarnings("deprecation") + private static TrustedCertificateSource getCertificateSourceCompat(Bundle bundle) + { + return (TrustedCertificateSource)bundle.getSerializable(EXTRA_CERTIFICATE_SOURCE); + } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index f49d84bc8..723967945 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -21,6 +21,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.security.KeyChain; import android.security.KeyChainAliasCallback; @@ -139,6 +140,19 @@ public class VpnProfileImportActivity extends AppCompatActivity { @Override public Loader onCreateLoader(int id, Bundle args) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) + { + return createCompat(args); + } + else + { + return new ProfileLoader(VpnProfileImportActivity.this, args.getParcelable(PROFILE_URI, Uri.class)); + } + } + + @SuppressWarnings("deprecation") + public Loader createCompat(Bundle args) { return new ProfileLoader(VpnProfileImportActivity.this, args.getParcelable(PROFILE_URI)); } From 5237bf3a5c6fa9eb13ad8ba416a7865876b0d6f7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 5 Feb 2025 17:00:25 +0100 Subject: [PATCH 10/14] android: Suppress deprecation warning because of startActivityAndCollapse() --- .../java/org/strongswan/android/ui/VpnTileService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java index 05c3e2da9..6f95a626a 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java @@ -187,7 +187,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt } else { - startActivityAndCollapse(intent); + startActivityAndCollapseCompat(intent); } } else @@ -214,10 +214,16 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt } else { - startActivityAndCollapse(intent); + startActivityAndCollapseCompat(intent); } } + @SuppressWarnings("deprecation") + private void startActivityAndCollapseCompat(Intent intent) + { + startActivityAndCollapse(intent); + } + @Override public void stateChanged() { From b021406f6b3372de9eb444a84594bb4176f34206 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 5 Feb 2025 17:02:42 +0100 Subject: [PATCH 11/14] android: Suppress deprecation warning because of stopForeground() --- .../android/logic/CharonVpnService.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index 03e59f7ee..314678ede 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -399,11 +399,24 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe public void run() { mShowNotification = false; - stopForeground(true); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) + { + stopForegroundCompat(); + } + else + { + stopForeground(STOP_FOREGROUND_REMOVE); + } } }); } + @SuppressWarnings("deprecation") + private void stopForegroundCompat() + { + stopForeground(true); + } + /** * Create a notification channel for Android 8+ */ From 0b6d42661d35061e8b0a4f417ce3171a295c79b5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Feb 2025 12:11:43 +0100 Subject: [PATCH 12/14] android: Consistently use *Start/End in layouts and remove redundant old settings --- .../main/res/layout/imc_state_fragment.xml | 10 +++--- .../main/res/layout/profile_detail_view.xml | 35 +++++-------------- .../main/res/layout/profile_import_view.xml | 17 +++------ .../main/res/layout/profile_list_fragment.xml | 6 ++-- .../res/layout/remediation_instruction.xml | 6 ++-- .../layout/remediation_instruction_item.xml | 8 ++--- .../res/layout/selected_application_item.xml | 6 ++-- .../res/layout/trusted_certificates_item.xml | 2 +- .../src/main/res/layout/two_line_button.xml | 2 +- .../main/res/layout/vpn_state_fragment.xml | 22 ++++++------ 10 files changed, 44 insertions(+), 70 deletions(-) diff --git a/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml b/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml index c1e278968..ba1762882 100644 --- a/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml +++ b/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml @@ -30,15 +30,15 @@ @@ -58,8 +58,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" - android:layout_marginLeft="20dp" - android:layout_marginRight="20dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" android:text="@string/show_remediation_instructions" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary" /> diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index 5197c63f7..eb67583bb 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -27,7 +27,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/state_background" - android:drawableStart="@android:drawable/ic_dialog_alert" + app:drawableStartCompat="@android:drawable/ic_dialog_alert" android:drawablePadding="8dp" android:padding="8dp" android:text="@string/alert_text_vpn_profile_read_only" @@ -73,7 +73,7 @@ @@ -140,7 +140,7 @@ @@ -153,8 +153,8 @@ android:id="@+id/install_user_certificate" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" android:text="@string/profile_user_certificate_install" /> @@ -162,7 +162,7 @@ @@ -210,7 +210,7 @@ @@ -319,14 +319,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:text="@string/profile_cert_req_label" /> @@ -336,14 +334,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:text="@string/profile_use_ocsp_label" /> @@ -353,14 +349,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:text="@string/profile_use_crl_label" /> @@ -370,14 +364,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:text="@string/profile_strict_revocation_label" /> @@ -387,14 +379,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:text="@string/profile_rsa_pss_label" /> @@ -404,14 +394,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:text="@string/profile_ipv6_transport_label" /> @@ -420,7 +408,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginLeft="4dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:text="@string/profile_split_tunneling_label" @@ -429,7 +416,7 @@ @@ -66,7 +64,6 @@ android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="4dp" android:layout_marginStart="4dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" /> @@ -75,7 +72,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" - android:layout_marginLeft="4dp" android:layout_marginStart="4dp" android:textSize="12sp" android:text="@string/profile_gateway_label" /> @@ -84,7 +80,6 @@ android:id="@+id/gateway" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="4dp" android:layout_marginStart="4dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" /> @@ -93,7 +88,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" - android:layout_marginLeft="4dp" android:layout_marginStart="4dp" android:textSize="12sp" android:text="@string/profile_vpn_type_label" /> @@ -103,7 +97,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="4dp" - android:layout_marginLeft="4dp" android:layout_marginStart="4dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" /> @@ -162,7 +155,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:layout_marginLeft="4dp" + android:layout_marginStart="4dp" android:textSize="12sp" android:text="@string/profile_user_certificate_label" /> @@ -174,8 +167,8 @@ android:id="@+id/import_user_certificate" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" android:text="@string/profile_cert_import" /> @@ -190,7 +183,7 @@ diff --git a/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml b/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml index 7308f6d48..6cc9d5b9c 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml @@ -19,8 +19,8 @@ android:layout_height="match_parent" android:paddingBottom="10dp" android:paddingTop="10dp" - android:paddingLeft="5dp" - android:paddingRight="5dp" > + android:paddingStart="5dp" + android:paddingEnd="5dp" > diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml index 8bae732f6..9a3899144 100644 --- a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml +++ b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml @@ -24,7 +24,7 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" android:textIsSelectable="true" android:textAppearance="?android:attr/textAppearanceLarge" /> @@ -32,7 +32,7 @@ android:id="@+id/description" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" android:textIsSelectable="true" android:textColor="?android:textColorSecondary" android:textAppearance="?android:attr/textAppearanceMedium" /> @@ -41,7 +41,7 @@ android:id="@+id/list_header" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" android:layout_marginTop="20dp" android:textIsSelectable="true" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml b/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml index cb14fe89a..ddd9f67a5 100644 --- a/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml +++ b/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml @@ -27,8 +27,8 @@ android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="15dp" - android:layout_marginRight="15dp" + android:layout_marginStart="15dp" + android:layout_marginEnd="15dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textIsSelectable="false" /> @@ -37,8 +37,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@android:id/text1" - android:layout_alignLeft="@android:id/text1" - android:layout_alignRight="@android:id/text1" + android:layout_alignStart="@android:id/text1" + android:layout_alignEnd="@android:id/text1" android:textColor="?android:textColorSecondary" android:textAppearance="?android:attr/textAppearanceSmall" android:singleLine="true" diff --git a/src/frontends/android/app/src/main/res/layout/selected_application_item.xml b/src/frontends/android/app/src/main/res/layout/selected_application_item.xml index aacbb3b09..1f666478a 100644 --- a/src/frontends/android/app/src/main/res/layout/selected_application_item.xml +++ b/src/frontends/android/app/src/main/res/layout/selected_application_item.xml @@ -19,8 +19,8 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingLeft="8dp" - android:paddingRight="8dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" android:minHeight="?android:listPreferredItemHeight" android:background="@drawable/activated_background" android:gravity="center_vertical" > @@ -29,7 +29,6 @@ android:duplicateParentState="true" android:layout_width="@android:dimen/app_icon_size" android:layout_height="@android:dimen/app_icon_size" - android:layout_marginRight="8dip" android:layout_marginEnd="8dip" android:scaleType="centerInside" /> @@ -47,7 +46,6 @@ android:duplicateParentState="true" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="16dp" android:layout_marginStart="16dp" /> diff --git a/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml b/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml index 6677a618a..7b54b6751 100644 --- a/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml +++ b/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml @@ -30,7 +30,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/subject_primary" - android:layout_alignLeft="@id/subject_primary" + android:layout_alignStart="@id/subject_primary" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary" /> diff --git a/src/frontends/android/app/src/main/res/layout/two_line_button.xml b/src/frontends/android/app/src/main/res/layout/two_line_button.xml index c8edc04c2..f7ceaf1c8 100644 --- a/src/frontends/android/app/src/main/res/layout/two_line_button.xml +++ b/src/frontends/android/app/src/main/res/layout/two_line_button.xml @@ -34,7 +34,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@android:id/text1" - android:layout_alignLeft="@android:id/text1" + android:layout_alignStart="@android:id/text1" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary" /> diff --git a/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml b/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml index 4616adf0a..9227007fb 100644 --- a/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml +++ b/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml @@ -34,8 +34,8 @@ android:id="@+id/vpn_error_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="20dp" - android:layout_marginRight="20dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" android:layout_marginTop="24dp" android:layout_marginBottom="12dp" android:text="Failed to establish VPN: Server is unreachable" @@ -53,7 +53,7 @@ android:id="@+id/show_log" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" android:text="@string/show_log" android:textColor="@color/primary" android:textSize="14sp" @@ -84,8 +84,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" - android:layout_marginLeft="20dp" - android:layout_marginRight="20dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" android:layout_marginTop="10dp" android:columnCount="2" android:rowCount="2" > @@ -93,7 +93,7 @@ @@ -146,8 +146,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" - android:layout_marginLeft="20dp" - android:layout_marginRight="20dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" android:text="@string/disconnect" style="?android:attr/borderlessButtonStyle" > From a47e282d09078f27e2b4a7a182c7f67d39c62c2c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 18 Feb 2025 13:26:08 +0100 Subject: [PATCH 13/14] android: Imported VPN profile files may contain passwords A warning is displayed to the users, reminding them that there is a cleartext password in the file. --- .../android/ui/VpnProfileImportActivity.java | 12 +++++++++++- .../src/main/res/layout/profile_import_view.xml | 14 ++++++++++++++ .../android/app/src/main/res/values-de/strings.xml | 1 + .../android/app/src/main/res/values-pl/strings.xml | 1 + .../android/app/src/main/res/values-ru/strings.xml | 1 + .../android/app/src/main/res/values-uk/strings.xml | 1 + .../app/src/main/res/values-zh-rCN/strings.xml | 1 + .../app/src/main/res/values-zh-rTW/strings.xml | 1 + .../android/app/src/main/res/values/strings.xml | 1 + 9 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index 723967945..08565d67b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -99,6 +99,7 @@ public class VpnProfileImportActivity extends AppCompatActivity private boolean mHideImport; private androidx.core.widget.ContentLoadingProgressBar mProgressBar; private TextView mExistsWarning; + private TextView mSharedSecretWarning; private ViewGroup mBasicDataGroup; private TextView mName; private TextView mGateway; @@ -206,6 +207,7 @@ public class VpnProfileImportActivity extends AppCompatActivity mProgressBar = findViewById(R.id.progress_bar); mExistsWarning = findViewById(R.id.exists_warning); + mSharedSecretWarning = findViewById(R.id.shared_secret_warning); mBasicDataGroup = findViewById(R.id.basic_data_group); mName = findViewById(R.id.name); mGateway = findViewById(R.id.gateway); @@ -224,6 +226,7 @@ public class VpnProfileImportActivity extends AppCompatActivity mRemoteCert = findViewById(R.id.remote_certificate); mExistsWarning.setVisibility(View.GONE); + mSharedSecretWarning.setVisibility(View.GONE); mBasicDataGroup.setVisibility(View.GONE); mUsernamePassword.setVisibility(View.GONE); mUserCertificate.setVisibility(View.GONE); @@ -400,10 +403,16 @@ public class VpnProfileImportActivity extends AppCompatActivity if (mProfile.getVpnType().has(VpnTypeFeature.USER_PASS)) { mUsername.setText(mProfile.getUsername()); - if (mProfile.getUsername() != null && !mProfile.getUsername().isEmpty()) + if (!TextUtils.isEmpty(mProfile.getUsername())) { mUsername.setEnabled(false); } + mPassword.setText(mProfile.getPassword()); + if (!TextUtils.isEmpty(mProfile.getPassword())) + { + mPassword.setEnabled(false); + mSharedSecretWarning.setVisibility(View.VISIBLE); + } } mUserCertificate.setVisibility(mProfile.getVpnType().has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE); @@ -523,6 +532,7 @@ public class VpnProfileImportActivity extends AppCompatActivity if (type.has(VpnTypeFeature.USER_PASS)) { profile.setUsername(local.optString("eap_id", null)); + profile.setPassword(local.optString("shared_secret", null)); } if (type.has(VpnTypeFeature.CERTIFICATE)) diff --git a/src/frontends/android/app/src/main/res/layout/profile_import_view.xml b/src/frontends/android/app/src/main/res/layout/profile_import_view.xml index bca3ac853..231b552c9 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_import_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_import_view.xml @@ -47,6 +47,20 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorPrimary" /> + + TLS-Handshake fehlgeschlagen Ungültiger Wert in \"%1$s\" Dieses VPN Profil existiert bereits, die bestehenden Einstellungen werden ersetzt. + Diese Datei enthält ein Klartext-Passwort. Denken Sie daran, sie nach dem Importieren zu löschen. Zertifikat aus VPN Profil importieren Zertifikat für \"%1$s\" Profil-ID diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index 68089da93..cdf4eccc1 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -138,6 +138,7 @@ TLS handshake failed Invalid value in \"%1$s\" This VPN profile already exists, its current settings will be replaced. + This file contains a cleartext password. Remember to delete it after importing. Import certificate from VPN profile Certificate for \"%1$s\" Profile ID diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index ab35f29e7..d879c9ebe 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -132,6 +132,7 @@ TLS handshake failed Invalid value in \"%1$s\" This VPN profile already exists, its current settings will be replaced. + This file contains a cleartext password. Remember to delete it after importing. Import certificate from VPN profile Certificate for \"%1$s\" Profile ID diff --git a/src/frontends/android/app/src/main/res/values-uk/strings.xml b/src/frontends/android/app/src/main/res/values-uk/strings.xml index 677d7c95d..8eadc5cb3 100644 --- a/src/frontends/android/app/src/main/res/values-uk/strings.xml +++ b/src/frontends/android/app/src/main/res/values-uk/strings.xml @@ -133,6 +133,7 @@ TLS handshake failed Invalid value in \"%1$s\" This VPN profile already exists, its current settings will be replaced. + This file contains a cleartext password. Remember to delete it after importing. Import certificate from VPN profile Certificate for \"%1$s\" Profile ID diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index 43d3134d9..4cc7295a2 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -132,6 +132,7 @@ TLS握手失败 无效的值: \"%1$s\" 此VPN配置已经存在,当前设定将被覆盖。 + This file contains a cleartext password. Remember to delete it after importing. 从VPN配置导入证书 \"%1$s\" 所对应的证书 配置文件ID diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index 731682f66..303679112 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -132,6 +132,7 @@ TLS連線失敗 Invalid value in \"%1$s\" 這個VPN設定檔已經存在,當前設定檔會被覆蓋。 + This file contains a cleartext password. Remember to delete it after importing. 從VPN設定檔匯入憑證 \"%1$s\" 對應的憑證 Profile ID diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index 71dc6e851..aaf7dd4c2 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -136,6 +136,7 @@ TLS handshake failed Invalid value in \"%1$s\" This VPN profile already exists, its current settings will be replaced. + This file contains a cleartext password. Remember to delete it after importing. Import certificate from VPN profile Certificate for \"%1$s\" Profile ID From f0f986c55d7658234144f6339ae76c4d17d93d1f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 4 Feb 2025 10:12:58 +0100 Subject: [PATCH 14/14] android: New release after adding support for passwords in managed configs and profile files Also fixes some bugs and deprecation warnings. --- src/frontends/android/app/build.gradle | 4 ++-- .../main/play/release-notes/de-DE/default.txt | 17 +++++------------ .../main/play/release-notes/en-US/default.txt | 17 +++++------------ 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 047ba8736..f513e52be 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -9,8 +9,8 @@ android { minSdkVersion 21 targetSdkVersion 34 - versionCode 84 - versionName "2.5.2" + versionCode 87 + versionName "2.5.3" externalNativeBuild { ndkBuild { diff --git a/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt b/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt index 5521e02a9..c6923513f 100644 --- a/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt +++ b/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt @@ -1,13 +1,6 @@ -# 2.5.2 # +# 2.5.3 # -- Ziel-SDK auf Android 14 erhöht -- Wegen eines Bugs in Android 14 ist eine zusätzliche Permission ist nötig, um von der Status-Kachel eine Verbindung im Hintergrund zu starten -- Fixt einen Crash beim Öffnen der Liste installierter Apps in neuen Profilen - -# 2.5.1 # - -- Fix für existierende Verknüpfungen und Automatisierung via Intents - -# 2.5.0 # - -- Unterstützung für verwaltete Konfigurationen via Enterprise Mobility Management (EMM) +- Unterstützt die Verteilung von Passwörtern in verwalteten Profilen +- Unterstützt den Import von Profil-Dateien mit Passwörtern +- Fixt einen Crash beim Ändern des Passworts von verwalteten Profilen +- Fixt einen Crash beim Importieren eines bereit existierenden Profils diff --git a/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt b/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt index 0dc823127..84153240a 100644 --- a/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt +++ b/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt @@ -1,13 +1,6 @@ -# 2.5.2 # +# 2.5.3 # -- Increased target SDK to Android 14 -- Due to a bug in Android 14, a new permission is necessary to start a profile in the background from the status tile -- Fix crash when listing installed apps for new profiles - -# 2.5.1 # - -- Fix for existing shortcuts and automation via Intents - -# 2.5.0 # - -- Support for managed configurations via enterprise mobility management (EMM) +- Add support for distributing passwords in managed profiles +- Add support for importing profile files with passwords +- Fix crash when editing password of managed profiles +- Fix crash when re-importing an already existing profile