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 +