From ff5fc29285899803ecbefc5c9441a3a9df2e54b1 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 28 Jul 2025 14:31:17 +0200
Subject: [PATCH 01/18] android: Update Gradle plugin
---
src/frontends/android/build.gradle | 2 +-
src/frontends/android/gradle/wrapper/gradle-wrapper.properties | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle
index 3fa6c71da..a35c5ffc3 100644
--- a/src/frontends/android/build.gradle
+++ b/src/frontends/android/build.gradle
@@ -4,7 +4,7 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.5.1'
+ classpath 'com.android.tools.build:gradle:8.13.0'
}
}
diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
index 8c8c77e3f..917b2e631 100644
--- a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
+++ b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
From 3eb2f58a5144644770712921935c00ae4b4b165b Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 28 Jul 2025 14:31:27 +0200
Subject: [PATCH 02/18] android: Update dependencies
---
src/frontends/android/app/build.gradle | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 9b59f448a..6db309f15 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -46,10 +46,10 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.1'
- implementation 'androidx.lifecycle:lifecycle-process:2.9.1'
+ implementation 'androidx.lifecycle:lifecycle-process:2.9.4'
implementation 'androidx.preference:preference:1.2.1'
- implementation 'com.google.android.material:material:1.12.0'
+ implementation 'com.google.android.material:material:1.13.0'
testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.assertj:assertj-core:3.24.2'
- testImplementation 'org.mockito:mockito-core:5.8.0'
+ testImplementation 'org.assertj:assertj-core:3.27.6'
+ testImplementation 'org.mockito:mockito-core:5.20.0'
}
From 216a9dbb8dabcd5add6134949462f2d7e6196fb0 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 14:31:26 +0200
Subject: [PATCH 03/18] android: Avoid crash if read-only profile selection
dialog is hidden
---
.../org/strongswan/android/ui/VpnProfileListFragment.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
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 9b25785f1..1679ef5bd 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
@@ -60,7 +60,6 @@ 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 implements MenuProvider
@@ -197,7 +196,10 @@ public class VpnProfileListFragment extends Fragment implements MenuProvider
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
- outState.putIntegerArrayList(SELECTED_KEY, new ArrayList<>(mSelected));
+ if (!mReadOnly)
+ {
+ outState.putIntegerArrayList(SELECTED_KEY, new ArrayList<>(mSelected));
+ }
}
@Override
From 2404b2bee632d852a03f710bf4e32ee4bddd9339 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 14:35:11 +0200
Subject: [PATCH 04/18] android: Apply UI changes for edge-to-edge views in
Android 15+
When targeting Android 15, edge-to-edge is the default and when targeting
Android 16, apps can't opt-out from this anymore. So we update our views
and enable edge-to-edge also for older versions (avoids the black bar
behind the system UI at the bottom). For most views we just use automatic
margins via android:fitsSystemWindows (or programmatically via
setDecorFitsSystemWindows). However, for the profile lists and log views,
we take some extra measures that allow the lists to go behind the bottom
system UI. Appropriate padding is applied at the bottom of the lists so
the last item(s) can be scrolled into full view.
---
src/frontends/android/app/build.gradle | 1 +
.../android/app/src/main/AndroidManifest.xml | 1 +
.../strongswan/android/ui/LogActivity.java | 4 ++
.../strongswan/android/ui/LogFragment.java | 3 ++
.../strongswan/android/ui/MainActivity.java | 4 ++
.../ui/RemediationInstructionsActivity.java | 3 ++
.../ui/SelectedApplicationsActivity.java | 3 ++
.../android/ui/SettingsActivity.java | 3 ++
.../ui/TrustedCertificateImportActivity.java | 1 +
.../ui/TrustedCertificatesActivity.java | 3 ++
.../android/ui/VpnProfileDetailActivity.java | 2 +
.../android/ui/VpnProfileImportActivity.java | 2 +
.../android/ui/VpnProfileListFragment.java | 3 ++
.../android/ui/VpnProfileSelectActivity.java | 4 ++
.../org/strongswan/android/utils/Utils.java | 42 +++++++++++++++++++
.../layout-large/remediation_instructions.xml | 3 +-
.../app/src/main/res/layout/log_activity.xml | 3 +-
.../app/src/main/res/layout/log_fragment.xml | 15 +++----
.../android/app/src/main/res/layout/main.xml | 3 +-
.../main/res/layout/profile_detail_view.xml | 3 +-
.../main/res/layout/profile_import_view.xml | 7 ++--
.../main/res/layout/profile_list_fragment.xml | 8 ++--
.../res/layout/remediation_instructions.xml | 3 +-
.../layout/trusted_certificates_activity.xml | 9 ++--
.../main/res/layout/vpn_profile_select.xml | 5 ++-
25 files changed, 114 insertions(+), 24 deletions(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 6db309f15..8991567a9 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -46,6 +46,7 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.1'
+ implementation 'androidx.core:core:1.17.0'
implementation 'androidx.lifecycle:lifecycle-process:2.9.4'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.13.0'
diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml
index 51c6ff89e..c0a8ed484 100644
--- a/src/frontends/android/app/src/main/AndroidManifest.xml
+++ b/src/frontends/android/app/src/main/AndroidManifest.xml
@@ -36,6 +36,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/ApplicationTheme"
+ android:windowSoftInputMode="adjustResize"
android:networkSecurityConfig="@xml/network_security_config"
android:enableOnBackInvokedCallback="true"
android:allowBackup="false" >
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java
index 40738fe63..c7d052ecd 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java
@@ -26,10 +26,12 @@ import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.LogContentProvider;
import org.strongswan.android.logic.CharonVpnService;
+import org.strongswan.android.utils.Utils;
import java.io.File;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
public class LogActivity extends AppCompatActivity
{
@@ -38,6 +40,8 @@ public class LogActivity extends AppCompatActivity
{
super.onCreate(savedInstanceState);
setContentView(R.layout.log_activity);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
index ca873a7fd..d83e1ec4a 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
@@ -30,6 +30,7 @@ import android.widget.ListView;
import org.strongswan.android.R;
import org.strongswan.android.logic.CharonVpnService;
+import org.strongswan.android.utils.Utils;
import java.io.BufferedReader;
import java.io.File;
@@ -81,6 +82,8 @@ public class LogFragment extends Fragment
mLog = view.findViewById(R.id.log);
mLog.setAdapter(mLogAdapter);
+ Utils.applyWindowInsetsAsPaddingForLists(mLog);
+
mScrollPosition = -1;
if (savedInstanceState != null)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
index 603dedef9..a1e647265 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
@@ -34,6 +34,7 @@ import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
+import org.strongswan.android.utils.Utils;
import java.io.File;
import java.util.ArrayList;
@@ -43,6 +44,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialogFragment;
+import androidx.core.view.WindowCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@@ -66,6 +68,8 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
ActionBar bar = getSupportActionBar();
bar.setDisplayShowHomeEnabled(true);
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 d1de55214..3e8bd03b1 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
@@ -18,6 +18,8 @@ package org.strongswan.android.ui;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
+
import android.view.MenuItem;
import org.strongswan.android.R;
@@ -33,6 +35,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
{
super.onCreate(savedInstanceState);
setContentView(R.layout.remediation_instructions);
+ WindowCompat.enableEdgeToEdge(getWindow());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState != null)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java
index d1b66e4ee..b3c23ffc8 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsActivity.java
@@ -26,6 +26,7 @@ import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
import androidx.fragment.app.FragmentManager;
public class SelectedApplicationsActivity extends AppCompatActivity
@@ -37,6 +38,8 @@ public class SelectedApplicationsActivity extends AppCompatActivity
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java
index d1b5190e4..eec782fb7 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsActivity.java
@@ -20,6 +20,7 @@ import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
public class SettingsActivity extends AppCompatActivity
{
@@ -28,6 +29,8 @@ public class SettingsActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
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 322e21e2c..a794e93ba 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
@@ -41,6 +41,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialogFragment;
+import androidx.core.view.WindowCompat;
import androidx.fragment.app.FragmentTransaction;
public class TrustedCertificateImportActivity extends AppCompatActivity
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
index 0a0b26a8a..c9e242881 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
@@ -33,6 +33,7 @@ import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
import org.strongswan.android.security.TrustedCertificateEntry;
import org.strongswan.android.ui.CertificateDeleteConfirmationDialog.OnCertificateDeleteListener;
+import org.strongswan.android.utils.Utils;
import java.security.KeyStore;
@@ -41,6 +42,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
@@ -71,6 +73,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
{
super.onCreate(savedInstanceState);
setContentView(R.layout.trusted_certificates_activity);
+ WindowCompat.enableEdgeToEdge(getWindow());
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index cf1789d4b..13e38273e 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -84,6 +84,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.text.HtmlCompat;
+import androidx.core.view.WindowCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class VpnProfileDetailActivity extends AppCompatActivity
@@ -199,6 +200,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mDataSource.open();
setContentView(R.layout.profile_detail_view);
+ WindowCompat.enableEdgeToEdge(getWindow());
mManagedProfile = findViewById(R.id.managed_profile);
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 ee56c1e53..f10ce0e14 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
@@ -78,6 +78,7 @@ import javax.net.ssl.SSLHandshakeException;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
@@ -204,6 +205,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
mDataSource.open();
setContentView(R.layout.profile_import_view);
+ WindowCompat.enableEdgeToEdge(getWindow());
mProgressBar = findViewById(R.id.progress_bar);
mExistsWarning = findViewById(R.id.exists_warning);
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 1679ef5bd..e845cc659 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
@@ -48,6 +48,7 @@ import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
import org.strongswan.android.utils.Constants;
+import org.strongswan.android.utils.Utils;
import java.util.ArrayList;
import java.util.HashSet;
@@ -148,6 +149,8 @@ public class VpnProfileListFragment extends Fragment implements MenuProvider
mListView.setEmptyView(view.findViewById(R.id.profile_list_empty));
mListView.setOnItemClickListener(mVpnProfileClicked);
+ Utils.applyWindowInsetsAsPaddingForLists(mListView);
+
if (!mReadOnly)
{
requireActivity().addMenuProvider(this, getViewLifecycleOwner());
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
index dd64d0c75..56aefbede 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
@@ -22,11 +22,13 @@ import android.os.Bundle;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
+import org.strongswan.android.utils.Utils;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
+import androidx.core.view.WindowCompat;
public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpnProfileSelectedListener
{
@@ -35,6 +37,8 @@ public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpn
{
super.onCreate(savedInstanceState);
setContentView(R.layout.vpn_profile_select);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
/* we should probably return a result also if the user clicks the back
* button before selecting a profile */
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java
index 5142ee1b5..ac1a315c4 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java
@@ -17,9 +17,16 @@
package org.strongswan.android.utils;
+import android.view.View;
+import android.view.ViewGroup;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
public class Utils
{
static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
@@ -75,4 +82,39 @@ public class Utils
}
return InetAddress.getByAddress(bytes);
}
+
+ /**
+ * Apply window insets for the system UI as margins except for the bottom,
+ * which is useful if the view ends with a list. WindowInsetsCompat.CONSUMED
+ * is not returned so padding can be applied to the list.
+ *
+ * @param view view to apply margins to
+ */
+ public static void applyWindowInsetsAsMarginsForLists(View view)
+ {
+ ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
+ ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
+ mlp.topMargin = insets.top;
+ mlp.leftMargin = insets.left;
+ mlp.rightMargin = insets.right;
+ v.setLayoutParams(mlp);
+ return windowInsets;
+ });
+ }
+
+ /**
+ * Apply bottom inset for the system UI as padding on the given (list) view
+ * so the last item can be scrolled fully into view.
+ *
+ * @param view view to apply padding to
+ */
+ public static void applyWindowInsetsAsPaddingForLists(View view)
+ {
+ ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPaddingRelative(0, 0, 0, insets.bottom);
+ return WindowInsetsCompat.CONSUMED;
+ });
+ }
}
diff --git a/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml b/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml
index 1c6494f6e..a2db35116 100644
--- a/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml
+++ b/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml
@@ -18,7 +18,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:baselineAligned="false" >
+ android:baselineAligned="false"
+ android:fitsSystemWindows="true" >
+ android:layout_height="match_parent"
+ android:id="@+id/layout" >
-
+ android:paddingBottom="0dp"
+ android:paddingTop="5dp"
+ android:paddingStart="5dp"
+ android:paddingEnd="5dp" >
+ android:transcriptMode="normal"
+ android:clipToPadding="false" />
-
-
-
+
diff --git a/src/frontends/android/app/src/main/res/layout/main.xml b/src/frontends/android/app/src/main/res/layout/main.xml
index 2aaf181b2..06932866f 100644
--- a/src/frontends/android/app/src/main/res/layout/main.xml
+++ b/src/frontends/android/app/src/main/res/layout/main.xml
@@ -17,7 +17,8 @@
+ android:orientation="vertical"
+ android:id="@+id/layout" >
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true" >
@@ -28,9 +28,11 @@
android:layout_height="match_parent"
android:dividerHeight="1dp"
android:divider="?android:attr/listDivider"
- android:scrollbarAlwaysDrawVerticalTrack="true" />
+ android:overScrollFooter="@android:color/transparent"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:clipToPadding="false" />
-
-
\ No newline at end of file
+
diff --git a/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml b/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml
index 1eee0590d..ee7ec1901 100644
--- a/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml
+++ b/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml
@@ -15,10 +15,11 @@
for more details.
-->
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:fitsSystemWindows="true">
+ android:orientation="vertical"
+ android:id="@+id/layout" >
-
\ No newline at end of file
+
From 86508cdf2b38a79dedebfbcb9f67c3f2b23f3c3a Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 14:50:13 +0200
Subject: [PATCH 05/18] android: Update NDK version
---
src/frontends/android/app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 8991567a9..c55e660dc 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -19,7 +19,7 @@ android {
}
}
- ndkVersion "27.2.12479018"
+ ndkVersion "27.3.13750724"
externalNativeBuild {
ndkBuild {
From 19ef347628e4024c29d573d9080dc47cd47859e0 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 14:50:25 +0200
Subject: [PATCH 06/18] android: Increase targetSdkVersion to 36 (Android 16)
---
src/frontends/android/app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index c55e660dc..5b63cc4ed 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -5,9 +5,9 @@ android {
defaultConfig {
applicationId "org.strongswan.android"
- compileSdk 34
+ compileSdk 36
minSdkVersion 21
- targetSdkVersion 34
+ targetSdkVersion 36
versionCode 91
versionName "2.5.6"
From 59b74c767a53b9745fc6847c3b8027644ceea556 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 15:18:02 +0200
Subject: [PATCH 07/18] android: Update syntax for three values Gradle warns
about
Others seem to be fine or even get an explicit error if changed, like
targetSdkVersion.
---
src/frontends/android/app/build.gradle | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 5b63cc4ed..330016150 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
android {
- namespace 'org.strongswan.android'
+ namespace = 'org.strongswan.android'
defaultConfig {
applicationId "org.strongswan.android"
- compileSdk 36
+ compileSdk = 36
minSdkVersion 21
targetSdkVersion 36
@@ -19,7 +19,7 @@ android {
}
}
- ndkVersion "27.3.13750724"
+ ndkVersion = "27.3.13750724"
externalNativeBuild {
ndkBuild {
From 13426bd2ea8e7b0dee4625ea1dfd4e3c42c93a64 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 15:38:26 +0200
Subject: [PATCH 08/18] android: Remove obsolete TargetApi attribute
---
.../main/java/org/strongswan/android/logic/CharonVpnService.java | 1 -
1 file changed, 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 69b217a2e..97e93e8e0 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
@@ -966,7 +966,6 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
return fd != null ? fd.detachFd() : -1;
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public synchronized void establishBlocking()
{
/* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
From 3bf34f1cd5ca0b380621f1ae8247e6e2f7ca838b Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 15:39:10 +0200
Subject: [PATCH 09/18] android: Simplify theme definition due to minSdkVersion
---
.../app/src/main/res/values-v15/styles.xml | 28 -------------------
.../app/src/main/res/values-v21/styles.xml | 25 -----------------
.../app/src/main/res/values/styles.xml | 4 +--
3 files changed, 2 insertions(+), 55 deletions(-)
delete mode 100644 src/frontends/android/app/src/main/res/values-v15/styles.xml
delete mode 100644 src/frontends/android/app/src/main/res/values-v21/styles.xml
diff --git a/src/frontends/android/app/src/main/res/values-v15/styles.xml b/src/frontends/android/app/src/main/res/values-v15/styles.xml
deleted file mode 100644
index 2b152c4c9..000000000
--- a/src/frontends/android/app/src/main/res/values-v15/styles.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/frontends/android/app/src/main/res/values-v21/styles.xml b/src/frontends/android/app/src/main/res/values-v21/styles.xml
deleted file mode 100644
index 050c3195d..000000000
--- a/src/frontends/android/app/src/main/res/values-v21/styles.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/frontends/android/app/src/main/res/values/styles.xml b/src/frontends/android/app/src/main/res/values/styles.xml
index 0ce04b1d9..8dc8e4499 100644
--- a/src/frontends/android/app/src/main/res/values/styles.xml
+++ b/src/frontends/android/app/src/main/res/values/styles.xml
@@ -16,7 +16,7 @@
-->
-
-
From 357b93e99df3ae75066260304352f09040bdb4fe Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Mon, 4 Aug 2025 15:42:21 +0200
Subject: [PATCH 10/18] android: Use correct title for proxy server port field
in managed configs
---
.../android/app/src/main/res/xml/managed_configuration.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 8c2a70eb8..2b2a57479 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
@@ -324,7 +324,7 @@
android:description="@string/managed_config_proxy_port_description"
android:key="proxy_port"
android:restrictionType="integer"
- android:title="@string/managed_config_proxy_port_description" />
+ android:title="@string/managed_config_proxy_port_title" />
Date: Mon, 4 Aug 2025 15:44:16 +0200
Subject: [PATCH 11/18] android: Remove redundant namespace and fix indentation
in state fragment layout
---
.../app/src/main/res/layout/vpn_state_fragment.xml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
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 9227007fb..b3d8cfdc6 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
@@ -74,11 +74,11 @@
-
+
Date: Thu, 25 Sep 2025 17:31:03 +0200
Subject: [PATCH 12/18] android: Explicitly pull in
androidx.localbroadcastmanager
Seems to not get pulled in automatically anymore. This has actually been
deprecated for years apparently. Unfortunately, it's not that easy to
replace, so keep it for now.
---
src/frontends/android/app/build.gradle | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 330016150..729b7dd82 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -49,6 +49,7 @@ dependencies {
implementation 'androidx.core:core:1.17.0'
implementation 'androidx.lifecycle:lifecycle-process:2.9.4'
implementation 'androidx.preference:preference:1.2.1'
+ implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
implementation 'com.google.android.material:material:1.13.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.27.6'
From 4918e681eef83d378593b9589809ea49b895ef7b Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Thu, 25 Sep 2025 18:37:15 +0200
Subject: [PATCH 13/18] android: Allow selecting a user certificate for managed
profiles
This is only enabled for profiles that are not distributed with a
certificate.
---
.../android/data/VpnProfileManagedDataSource.java | 15 +++++++++++----
.../android/ui/VpnProfileDetailActivity.java | 13 ++++++++++---
.../app/src/main/res/values-de/strings.xml | 2 +-
.../app/src/main/res/values-pl/strings.xml | 2 +-
.../app/src/main/res/values-ru/strings.xml | 2 +-
.../app/src/main/res/values-uk/strings.xml | 2 +-
.../app/src/main/res/values-zh-rCN/strings.xml | 2 +-
.../app/src/main/res/values-zh-rTW/strings.xml | 2 +-
.../android/app/src/main/res/values/strings.xml | 2 +-
9 files changed, 28 insertions(+), 14 deletions(-)
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 0f6ec6292..eaf0cfaac 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
@@ -33,6 +33,7 @@ import java.util.UUID;
public class VpnProfileManagedDataSource implements VpnProfileDataSource
{
private static final String NAME_MANAGED_VPN_PROFILES = "org.strongswan.android.data.VpnProfileManagedDataSource.preferences";
+ private static final String PREFIX_USER_CERT = "usercert:";
private final ManagedConfigurationService mManagedConfigurationService;
private final SharedPreferences mSharedPreferences;
@@ -52,11 +53,14 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource
@Override
public void close()
{
- /* remove passwords that are no longer referenced by a VPN profile */
- final Set actualKeys = mManagedConfigurationService.getManagedProfiles().keySet();
-
+ /* remove settings not referenced by a VPN profile */
final Set storedKeys = new HashSet<>(mSharedPreferences.getAll().keySet());
- storedKeys.removeAll(actualKeys);
+
+ for (String uuid : mManagedConfigurationService.getManagedProfiles().keySet())
+ {
+ storedKeys.remove(uuid);
+ storedKeys.remove(PREFIX_USER_CERT + uuid);
+ }
final SharedPreferences.Editor editor = mSharedPreferences.edit();
for (String key : storedKeys)
@@ -84,6 +88,7 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource
final SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(profile.getUUID().toString(), profile.getPassword());
+ editor.putString(PREFIX_USER_CERT + profile.getUUID().toString(), profile.getUserCertificateAlias());
return editor.commit();
}
@@ -100,8 +105,10 @@ public class VpnProfileManagedDataSource implements VpnProfileDataSource
private VpnProfile prepareVpnProfile(VpnProfile managedProfile)
{
final String password = mSharedPreferences.getString(managedProfile.getUUID().toString(), managedProfile.getPassword());
+ final String alias = mSharedPreferences.getString(PREFIX_USER_CERT + managedProfile.getUUID().toString(), managedProfile.getUserCertificateAlias());
final VpnProfile vpnProfile = managedProfile.clone();
vpnProfile.setPassword(password);
+ vpnProfile.setUserCertificateAlias(alias);
vpnProfile.setDataSource(this);
return vpnProfile;
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 13e38273e..aeef179ce 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -54,6 +54,7 @@ import android.widget.Spinner;
import android.widget.TextView;
import org.strongswan.android.R;
+import org.strongswan.android.data.ManagedVpnProfile;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
@@ -817,7 +818,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
alias = mProfile.getCertificateAlias();
getSupportActionBar().setTitle(mProfile.getName());
- setReadOnly(mProfile.isReadOnly());
+ setReadOnly(mProfile);
}
else
{
@@ -882,8 +883,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
}
- private void setReadOnly(final boolean readOnly)
+ private void setReadOnly(final VpnProfile profile)
{
+ final boolean readOnly = profile.isReadOnly();
+
mManagedProfile.setVisibility(readOnly ? View.VISIBLE : View.GONE);
mName.setEnabled(!readOnly);
@@ -920,8 +923,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
if (readOnly)
{
+ ManagedVpnProfile managedProfile = (ManagedVpnProfile)profile;
mSelectCert.setOnClickListener(null);
- mSelectUserCert.setOnClickListener(null);
+ if (managedProfile.getUserCertificate() != null)
+ {
+ mSelectUserCert.setOnClickListener(null);
+ }
}
}
diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml
index 6047ed63f..3691a598c 100644
--- a/src/frontends/android/app/src/main/res/values-de/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-de/strings.xml
@@ -158,7 +158,7 @@
Bitte geben Sie mit Leerzeichen getrennte, gültige Subnetzte und/oder IP-Adressen ein
Bitte geben Sie mit Leerzeichen getrennte, gültige IP-Adressen ein
Bitte geben Sie eine mit Bindestrichen getrennte, gültige Liste von Algorithmen ein
- Dieses Profil wird von Ihrem Administrator verwaltet und kann nicht bearbeitet werden. Nur das Passwort kann geändert werden
+ Dieses Profil wird von Ihrem Administrator verwaltet und kann nicht bearbeitet werden. Nur das Passwort bzw. Benutzer-Zertifikat kann geändert werden
EAP-TNC kann Ihre Privatsphäre beeinträchtigen
Gerätedaten werden an den Server-Betreiber gesendet
Trusted Network Connect (TNC) erlaubt Server-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.
Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.
Solche Daten werden nur übermittelt nachdem die Identität des Servers geprüft wurde.]]>
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 8d98fe36b..4a3b92201 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
@@ -160,7 +160,7 @@
Please enter valid subnets and/or IP addresses, separated by spaces
Please enter valid IP addresses, separated by spaces
Please enter a valid list of algorithms, separated by hyphens
- This VPN profile is managed by your administrator and can\'t be modified. You can only change the password
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the password or user certificate
EAP-TNC may affect your privacy
Device data is sent to the server operator
Trusted Network Connect (TNC) allows server operators to assess the health of a client device.For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
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 0f8c724ad..bb7173e92 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
@@ -154,7 +154,7 @@
Please enter valid subnets and/or IP addresses, separated by spaces
Please enter valid IP addresses, separated by spaces
Please enter a valid list of algorithms, separated by hyphens
- This VPN profile is managed by your administrator and can\'t be modified. You can only change the password
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the password or user certificate
EAP-TNC may affect your privacy
Device data is sent to the server operator
Trusted Network Connect (TNC) allows server operators to assess the health of a client device.For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
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 2b552b606..c92f170b2 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
@@ -155,7 +155,7 @@
Please enter valid subnets and/or IP addresses, separated by spaces
Please enter valid IP addresses, separated by spaces
Please enter a valid list of algorithms, separated by hyphens
- This VPN profile is managed by your administrator and can\'t be modified. You can only change the password
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the password or user certificate
EAP-TNC may affect your privacy
Device data is sent to the server operator
Trusted Network Connect (TNC) allows server operators to assess the health of a client device.For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
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 fe9602836..d2b21f227 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
@@ -154,7 +154,7 @@
请输入有效的子网和/或IP地址,用空格分隔
请输入有效的IP地址,以空格分隔
请输入用连字符分隔的有效算法列表
- This VPN profile is managed by your administrator and can\'t be modified. You can only change the password
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the password or user certificate
EAP-TNC可能会影响您的隐私
设备数据已被发送至服务器管理员
可信网络连接t (TNC) 允许服务器管理员评定一个用户设备的状况。出于此目的,服务器管理员可能要求以下数据如独立ID、已安装软件列表、系统设置、或加密过的文件校验值。
任何数据都仅将在验证过服务器的身份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 26f3e80a1..6c74fc786 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
@@ -154,7 +154,7 @@
Please enter valid subnets and/or IP addresses, separated by spaces
Please enter valid IP addresses, separated by spaces
Please enter a valid list of algorithms, separated by hyphens
- This VPN profile is managed by your administrator and can\'t be modified. You can only change the password
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the password or user certificate
EAP-TNC可能會影響您的隱私安全
裝置資料已經發送給伺服器管理者
Trusted Network Connect (TNC) 可以讓伺服器管理者評估用戶裝置的狀況。在這個目的下,伺服器管理者可能會要求以下資料,例如ID、已安裝的App項目、系統設定、或加密檔案驗證值。
任何資料都只有在驗證伺服器的身分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 7f031bec8..667375864 100644
--- a/src/frontends/android/app/src/main/res/values/strings.xml
+++ b/src/frontends/android/app/src/main/res/values/strings.xml
@@ -158,7 +158,7 @@
Please enter valid subnets and/or IP addresses, separated by spaces
Please enter valid IP addresses, separated by spaces
Please enter a valid list of algorithms, separated by hyphens
- This VPN profile is managed by your administrator and can\'t be modified. You can only change the password
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the password or user certificate
EAP-TNC may affect your privacy
Device data is sent to the server operator
Trusted Network Connect (TNC) allows server operators to assess the health of a client device.For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
From 1a16b2c0cb1ac2eab78cb6a94ce73c570c58a22d Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Fri, 26 Sep 2025 13:49:53 +0200
Subject: [PATCH 14/18] android: Don't print "null" if no user certificate is
selected
---
.../org/strongswan/android/ui/adapter/VpnProfileAdapter.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
index c2942b8a1..097c422a1 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
@@ -86,7 +86,8 @@ public class VpnProfileAdapter extends ArrayAdapter
tv = vpnProfileView.findViewById(R.id.profile_item_certificate);
if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
{
- tv.setText(getContext().getString(R.string.profile_user_certificate_label) + ": " + profile.getUserCertificateAlias());
+ String alias = profile.getUserCertificateAlias();
+ tv.setText(getContext().getString(R.string.profile_user_certificate_label) + ": " + (alias != null ? alias : ""));
tv.setVisibility(View.VISIBLE);
}
else
From 11f043c3dee948d8f4b72e0f97494cca3438ae90 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Tue, 30 Sep 2025 14:19:12 +0200
Subject: [PATCH 15/18] android: Prompt the user to edit the profile if no
certificate is selected
---
.../android/ui/VpnProfileControlActivity.java | 40 +++++++++++++++++++
.../app/src/main/res/values-de/strings.xml | 2 +
.../app/src/main/res/values-pl/strings.xml | 2 +
.../app/src/main/res/values-ru/strings.xml | 2 +
.../app/src/main/res/values-uk/strings.xml | 2 +
.../src/main/res/values-zh-rCN/strings.xml | 2 +
.../src/main/res/values-zh-rTW/strings.xml | 2 +
.../app/src/main/res/values/strings.xml | 2 +
8 files changed, 54 insertions(+)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
index a7b39b6ac..9ff07175e 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
@@ -70,6 +70,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
private static final String WAITING_FOR_RESULT = "WAITING_FOR_RESULT";
private static final String PROFILE_NAME = "PROFILE_NAME";
private static final String PROFILE_REQUIRES_PASSWORD = "REQUIRES_PASSWORD";
+ private static final String PROFILE_CERTIFICATE_MISSING = "CERTIFICATE_MISSING";
private static final String PROFILE_RECONNECT = "RECONNECT";
private static final String PROFILE_DISCONNECT = "DISCONNECT";
private static final String DIALOG_TAG = "Dialog";
@@ -332,6 +333,8 @@ public class VpnProfileControlActivity extends AppCompatActivity
profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
+ profileInfo.putBoolean(PROFILE_CERTIFICATE_MISSING, profile.getVpnType().has(VpnTypeFeature.CERTIFICATE) &&
+ profile.getUserCertificateAlias() == null);
profileInfo.putString(PROFILE_NAME, profile.getName());
removeFragmentByTag(DIALOG_TAG);
@@ -355,6 +358,13 @@ public class VpnProfileControlActivity extends AppCompatActivity
*/
private void startVpnProfile(Bundle profileInfo)
{
+ if (profileInfo.getBoolean(PROFILE_CERTIFICATE_MISSING))
+ {
+ CertificateRequiredDialog dialog = new CertificateRequiredDialog();
+ dialog.setArguments(profileInfo);
+ dialog.show(getSupportFragmentManager(), DIALOG_TAG);
+ return;
+ }
if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) &&
profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null)
{
@@ -621,6 +631,36 @@ public class VpnProfileControlActivity extends AppCompatActivity
}
}
+ /**
+ * Class that displays a dialog warning about a missing certificate.
+ */
+ public static class CertificateRequiredDialog extends AppCompatDialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final Bundle profileInfo = getArguments();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.certificate_required_title)
+ .setMessage(R.string.certificate_required_text)
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> getActivity().finish())
+ .setPositiveButton(R.string.edit_profile, (dialog, which) -> {
+ Intent editIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
+ editIntent.putExtra(VpnProfileDataSource.KEY_UUID, profileInfo.getString(VpnProfileDataSource.KEY_UUID));
+ startActivity(editIntent);
+ getActivity().finish();
+ });
+ return builder.create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+
/**
* Class that displays a warning before asking the user to add the app to the
* device's power whitelist.
diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml
index 3691a598c..c282004af 100644
--- a/src/frontends/android/app/src/main/res/values-de/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-de/strings.xml
@@ -234,6 +234,8 @@
Wiederholen abbrechen
Akku-Optimierung deaktivieren
Bitte den nächsten Dialog bestätigen, um die App auf die weisse Liste für Akku-Optimierung zu setzen, so dass sie NAT keepalives und Rekeyings zeitlich korrekt planen kann, um konstant erreichbar zu bleiben während die VPN-Verbindung besteht.
+ Benutzer-Zertifikat benötigt
+ Bitte bearbeiten Sie das VPN Profil, um eines auszuwählen.
VPN umschalten
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 4a3b92201..82d965536 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
@@ -236,6 +236,8 @@
Cancel retry
Disable battery optimizations
Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.
+ User certificate required
+ Please edit the VPN profile to select one.
Toggle VPN
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 bb7173e92..ac09e3e4c 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
@@ -230,6 +230,8 @@
Cancel retry
Disable battery optimizations
Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.
+ User certificate required
+ Please edit the VPN profile to select one.
Toggle VPN
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 c92f170b2..a819bc83e 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
@@ -231,6 +231,8 @@
Cancel retry
Disable battery optimizations
Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.
+ User certificate required
+ Please edit the VPN profile to select one.
Toggle VPN
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 d2b21f227..a7ad193c7 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
@@ -230,6 +230,8 @@
取消重试
禁用电池优化
请确认下一个对话框,将应用程序添加到设备的电源白名单中,这样它就可以忽略电池优化,并准确地安排NAT保持有效和重新键入,以便在建立VPN时始终保持可访问性。
+ User certificate required
+ Please edit the VPN profile to select one.
切换VPN
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 6c74fc786..533167584 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
@@ -230,6 +230,8 @@
Cancel retry
Disable battery optimizations
Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.
+ User certificate required
+ Please edit the VPN profile to select one.
Toggle VPN
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 667375864..417463606 100644
--- a/src/frontends/android/app/src/main/res/values/strings.xml
+++ b/src/frontends/android/app/src/main/res/values/strings.xml
@@ -234,6 +234,8 @@
Cancel retry
Disable battery optimizations
Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established.
+ User certificate required
+ Please edit the VPN profile to select one.
Toggle VPN
From cde39f4c1ac0cc2e25a1143bca3725bc963eb99f Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Tue, 30 Sep 2025 16:47:42 +0200
Subject: [PATCH 16/18] android: Make a certificate alias pre-selectable in
managed profiles
This allows associating a pre-installed certificate/key with a VPN
profile. For instance, one locally generated on the device via
SCEP/ETS by the MDM. It only works if the app is granted access to the
certificate/key (alias) by the MDM.
For now, users may still select a different certificate if one is
available (since this requires the certificate to be installed as
user-selectable there might not be).
---
.../org/strongswan/android/data/ManagedVpnProfile.java | 5 +++++
.../main/res/values-de/strings_managed_configuration.xml | 3 +++
.../main/res/values-pl/strings_managed_configuration.xml | 3 +++
.../main/res/values-ru/strings_managed_configuration.xml | 3 +++
.../main/res/values-uk/strings_managed_configuration.xml | 3 +++
.../res/values-zh-rCN/strings_managed_configuration.xml | 3 +++
.../res/values-zh-rTW/strings_managed_configuration.xml | 3 +++
.../src/main/res/values/strings_managed_configuration.xml | 2 ++
.../android/app/src/main/res/xml/managed_configuration.xml | 7 +++++++
9 files changed, 32 insertions(+)
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 47e85a5ca..dfe519a23 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
@@ -38,6 +38,7 @@ public class ManagedVpnProfile extends VpnProfile
private static final String KEY_REMOTE_REVOCATION_CRL_FLAG = "remote_revocation_crl";
private static final String KEY_REMOTE_REVOCATION_OCSP_FLAG = "remote_revocation_ocsp";
private static final String KEY_REMOTE_REVOCATION_STRICT_FLAG = "remote_revocation_strict";
+ private static final String KEY_LOCAL_USER_CERTIFICATE_ALIAS = "user_certificate_alias";
private static final String KEY_LOCAL_RSA_PSS_FLAG = "local_rsa_pss";
private static final String KEY_SPLIT_TUNNELLING_BLOCK_IPV4_FLAG = "split_tunnelling_block_ipv4";
@@ -152,6 +153,10 @@ public class ManagedVpnProfile extends VpnProfile
userCertificate = new ManagedUserCertificate(uuid.toString(), userCertificateData, userCertificatePassword);
setUserCertificateAlias(userCertificate.getAlias());
}
+ else
+ {
+ setUserCertificateAlias(getString(local, KEY_LOCAL_USER_CERTIFICATE_ALIAS));
+ }
flags = addPositiveFlag(flags, local, KEY_LOCAL_RSA_PSS_FLAG, VpnProfile.FLAGS_RSA_PSS);
return flags;
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 c7d516011..c0bdd7f70 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
@@ -1,5 +1,6 @@