diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 9b59f448a..f684b1019 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -1,16 +1,16 @@ apply plugin: 'com.android.application' android { - namespace 'org.strongswan.android' + namespace = 'org.strongswan.android' defaultConfig { applicationId "org.strongswan.android" - compileSdk 34 + compileSdk = 36 minSdkVersion 21 - targetSdkVersion 34 + targetSdkVersion 36 - versionCode 91 - versionName "2.5.6" + versionCode 94 + versionName "2.6.0" externalNativeBuild { ndkBuild { @@ -19,7 +19,7 @@ android { } } - ndkVersion "27.2.12479018" + ndkVersion = "27.3.13750724" externalNativeBuild { ndkBuild { @@ -46,10 +46,12 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.7.1' - implementation 'androidx.lifecycle:lifecycle-process:2.9.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.12.0' + 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.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' } 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/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/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/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) */ 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/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/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index cf1789d4b..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; @@ -84,6 +85,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 +201,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity mDataSource.open(); setContentView(R.layout.profile_detail_view); + WindowCompat.enableEdgeToEdge(getWindow()); mManagedProfile = findViewById(R.id.managed_profile); @@ -815,7 +818,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity alias = mProfile.getCertificateAlias(); getSupportActionBar().setTitle(mProfile.getName()); - setReadOnly(mProfile.isReadOnly()); + setReadOnly(mProfile); } else { @@ -880,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); @@ -918,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/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 9b25785f1..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; @@ -60,7 +61,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 @@ -149,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()); @@ -197,7 +199,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 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/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 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/play/release-notes/de-DE/default.txt b/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt index 9409e3a19..8650f2060 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,18 +1,11 @@ +# 2.6.0 # + +- Erlaubt die Vorauswahl eines Benutzer-Zertifikats via Alias in verwalteten Profilen +- Erlaubt die Auswahl von Benutzer-Zertifikaten in verwalteten Profilen, die kein eigenes Zertifikat installieren +- Fixt das Lesen von Split-Tunneling Settings in verwalteten Profilen +- Anpassungen für die randlose Anzeige, welche erzwungen wird, wenn Android 16 als Ziel-SDK verwendet wird +- Ziel-SDK auf Android 16 erhöht + # 2.5.6 # - Unterstützt benutzerdefinierte HTTP Proxy-Server (Android 10+) - -# 2.5.5 # - -- Fixt den Start von verwalteten Profilen als Always-on VPN - -# 2.5.4 # - -- Fixt Probleme beim Wiederherstellen der Verbindung - -# 2.5.3 # - -- 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 2ae778052..c9d90c16e 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,18 +1,11 @@ +# 2.6.0 # + +- Allow pre-selecting a user certificate via alias in managed profiles +- Allow selecting a user certificate for managed profiles that don't install their own certificate +- Fix reading split-tunneling settings in managed profiles +- Adapt to edge-to-edge display, which becomes mandatory when targeting Android 16 +- Increase target SDK to Android 16 + # 2.5.6 # - Add support for custom HTTP proxy server (Android 10+) - -# 2.5.5 # - -- Fix initiating managed profiles as Always-on VPN - -# 2.5.4 # - -- Fix issues when reestablishing the connection - -# 2.5.3 # - -- 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 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 + 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 @@ - + 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.]]>
@@ -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-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 @@ Toggle VPN 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 c7d516011..c0bdd7f70 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 @@ -1,5 +1,6 @@ Toggle VPN 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 c7d516011..c0bdd7f70 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 @@ -1,5 +1,6 @@ Toggle VPN 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 c7d516011..c0bdd7f70 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 @@ -1,5 +1,6 @@ - - - - - - - \ 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-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index fe9602836..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 @@ -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之后被发出。]]>
@@ -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-rCN/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml index c7d516011..c0bdd7f70 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 @@ -1,5 +1,6 @@ Toggle VPN 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 c7d516011..c0bdd7f70 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 @@ -1,5 +1,6 @@ Toggle VPN 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 04922033d..c0bdd7f70 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 @@ -94,6 +94,8 @@ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile User certificate password (Optional) Password required to extract the private key of the PKCS#12-container for installation + User certificate alias (Optional) + Alias of a user certificate/key already installed on the device. The app must be granted access to it by the MDM. Only used if no certificate is distributed with the config. If a certificate is required and this is not set, the user may select a local certificate manually @string/profile_rsa_pss_label @string/profile_rsa_pss_hint 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 @@ --> - - 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..aae843f8b 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 @@ -205,6 +205,13 @@ android:restrictionType="string" android:title="@string/managed_config_local_p12_password_title" /> + + @@ -324,7 +331,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" />