dependencies {
implementation 'androidx.appcompat:appcompat:1.7.1'
+ implementation 'androidx.core:core:1.17.0-rc01'
implementation 'androidx.lifecycle:lifecycle-process:2.9.2'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.12.0'
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" >
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
{
{
super.onCreate(savedInstanceState);
setContentView(R.layout.log_activity);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
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;
mLog = view.findViewById(R.id.log);
mLog.setAdapter(mLogAdapter);
+ Utils.applyWindowInsetsAsPaddingForLists(mLog);
+
mScrollPosition = -1;
if (savedInstanceState != null)
{
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;
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;
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ Utils.applyWindowInsetsAsMarginsForLists(findViewById(R.id.layout));
ActionBar bar = getSupportActionBar();
bar.setDisplayShowHomeEnabled(true);
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
+
import android.view.MenuItem;
import org.strongswan.android.R;
{
super.onCreate(savedInstanceState);
setContentView(R.layout.remediation_instructions);
+ WindowCompat.enableEdgeToEdge(getWindow());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState != null)
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
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
public class SettingsActivity extends AppCompatActivity
{
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
+ WindowCompat.enableEdgeToEdge(getWindow());
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
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
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;
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;
{
super.onCreate(savedInstanceState);
setContentView(R.layout.trusted_certificates_activity);
+ WindowCompat.enableEdgeToEdge(getWindow());
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
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
mDataSource.open();
setContentView(R.layout.profile_detail_view);
+ WindowCompat.enableEdgeToEdge(getWindow());
mManagedProfile = findViewById(R.id.managed_profile);
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;
mDataSource.open();
setContentView(R.layout.profile_import_view);
+ WindowCompat.enableEdgeToEdge(getWindow());
mProgressBar = findViewById(R.id.progress_bar);
mExistsWarning = findViewById(R.id.exists_warning);
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;
mListView.setEmptyView(view.findViewById(R.id.profile_list_empty));
mListView.setOnItemClickListener(mVpnProfileClicked);
+ Utils.applyWindowInsetsAsPaddingForLists(mListView);
+
if (!mReadOnly)
{
requireActivity().addMenuProvider(this, getViewLifecycleOwner());
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
{
{
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 */
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();
}
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;
+ });
+ }
}
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:baselineAligned="false" >
+ android:baselineAligned="false"
+ android:fitsSystemWindows="true" >
<fragment
class="org.strongswan.android.ui.RemediationInstructionsFragment"
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ android:layout_height="match_parent"
+ android:id="@+id/layout" >
<fragment
class="org.strongswan.android.ui.LogFragment"
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical" >
+ android:paddingBottom="0dp"
+ android:paddingTop="5dp"
+ android:paddingStart="5dp"
+ android:paddingEnd="5dp" >
<ListView
android:id="@+id/log"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_margin="10dp"
android:dividerHeight="0dp"
android:divider="@null"
android:fadeScrollbars="false"
android:scrollbarFadeDuration="0"
android:scrollbarAlwaysDrawVerticalTrack="true"
- android:transcriptMode="normal">
+ android:transcriptMode="normal"
+ android:clipToPadding="false" />
- </ListView>
-
-</LinearLayout>
+</FrameLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical" >
+ android:orientation="vertical"
+ android:id="@+id/layout" >
<fragment
class="org.strongswan.android.ui.VpnStateFragment"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
<TextView
android:id="@+id/managed_profile"
for more details.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true" >
<LinearLayout
android:layout_width="match_parent"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingBottom="10dp"
+ android:paddingBottom="0dp"
android:paddingTop="10dp"
android:paddingStart="5dp"
android:paddingEnd="5dp" >
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" />
- <TextView android:id="@+id/profile_list_empty"
+ <TextView android:id="@+id/profile_list_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="15dp"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:id="@+id/fragment_container">
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
for more details.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
+ 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">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical" >
+ android:orientation="vertical"
+ android:id="@+id/layout" >
<fragment
class="org.strongswan.android.ui.VpnProfileListFragment"
android:layout_weight="1"
app:read_only="true" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>