diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index c9443ff49..cab3a6c6d 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -7,10 +7,10 @@ android {
applicationId "org.strongswan.android"
compileSdk 34
minSdkVersion 21
- targetSdkVersion 33
+ targetSdkVersion 34
- versionCode 82
- versionName "2.5.1"
+ versionCode 84
+ versionName "2.5.2"
externalNativeBuild {
ndkBuild {
@@ -45,10 +45,10 @@ android {
}
dependencies {
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
+ implementation 'androidx.appcompat:appcompat:1.7.0'
+ implementation 'androidx.lifecycle:lifecycle-process:2.8.3'
implementation 'androidx.preference:preference:1.2.1'
- implementation 'com.google.android.material:material:1.10.0'
+ implementation 'com.google.android.material:material:1.12.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'org.mockito:mockito-core:5.8.0'
diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml
index ec4bf65cf..a5e6aabc2 100644
--- a/src/frontends/android/app/src/main/AndroidManifest.xml
+++ b/src/frontends/android/app/src/main/AndroidManifest.xml
@@ -22,9 +22,11 @@
+
+
@@ -176,10 +178,14 @@
+
mJobs;
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
public Scheduler(Context context)
{
mContext = context;
@@ -45,7 +47,14 @@ public class Scheduler extends BroadcastReceiver
IntentFilter filter = new IntentFilter();
filter.addAction(EXECUTE_JOB);
- mContext.registerReceiver(this, filter);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ {
+ mContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
+ }
+ else
+ {
+ mContext.registerReceiver(this, filter);
+ }
}
/**
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java
index 257f407ff..c60419005 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012-2024 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
*
@@ -20,6 +20,8 @@ package org.strongswan.android.logic;
import android.util.Log;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
@@ -27,10 +29,9 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
-import java.util.Observable;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-public class TrustedCertificateManager extends Observable
+public class TrustedCertificateManager
{
private static final String TAG = TrustedCertificateManager.class.getSimpleName();
private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
@@ -38,6 +39,7 @@ public class TrustedCertificateManager extends Observable
private volatile boolean mReload;
private boolean mLoaded;
private final ArrayList mKeyStores = new ArrayList();
+ private PropertyChangeSupport mObservers = new PropertyChangeSupport(this);
public enum TrustedCertificateSource
{
@@ -98,6 +100,35 @@ public class TrustedCertificateManager extends Observable
return Singleton.mInstance;
}
+ /**
+ * Add an observer for changes to the trusted certificate store. There will
+ * be a "storeChanged" property "change" when anything in the store changed.
+ *
+ * @param observer observer to add
+ */
+ public void addObserver(PropertyChangeListener observer)
+ {
+ mObservers.addPropertyChangeListener(observer);
+ }
+
+ /**
+ * Remove an observer for changes to the trusted certificate store.
+ *
+ * @param observer observer to remove
+ */
+ public void deleteObserver(PropertyChangeListener observer)
+ {
+ mObservers.removePropertyChangeListener(observer);
+ }
+
+ /**
+ * Use a fake property with a forced change to notify observers.
+ */
+ private void notifyObservers()
+ {
+ mObservers.firePropertyChange("storeChanged", false, true);
+ }
+
/**
* Invalidates the current load state so that the next call to load()
* will force a reload of the cached CA certificates.
@@ -110,7 +141,6 @@ public class TrustedCertificateManager extends Observable
{
Log.d(TAG, "Force reload of cached CA certificates on next load");
this.mReload = true;
- this.setChanged();
this.notifyObservers();
return this;
}
@@ -152,7 +182,6 @@ public class TrustedCertificateManager extends Observable
this.mCACerts = certs;
if (!this.mLoaded)
{
- this.setChanged();
this.notifyObservers();
this.mLoaded = true;
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java
index 3b5730773..1ceba87f7 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java
@@ -33,14 +33,14 @@ import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificate
import org.strongswan.android.security.TrustedCertificateEntry;
import org.strongswan.android.ui.adapter.TrustedCertificateAdapter;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;
-import java.util.Observable;
-import java.util.Observer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -249,12 +249,12 @@ public class TrustedCertificateListFragment extends ListFragment implements Load
}
}
- private class TrustedCertificateManagerObserver implements Observer
+ private class TrustedCertificateManagerObserver implements PropertyChangeListener
{
private ForceLoadContentObserver mContentObserver = new ForceLoadContentObserver();
@Override
- public void update(Observable observable, Object data)
+ public void propertyChange(PropertyChangeEvent evt)
{
mContentObserver.onChange(false);
}
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 4a6aac45f..d33a16d07 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
@@ -380,7 +380,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
- intent.putExtra(VpnProfileDataSource.KEY_READ_ONLY, mProfile.isReadOnly());
+ intent.putExtra(VpnProfileDataSource.KEY_READ_ONLY, mProfile != null && mProfile.isReadOnly());
mSelectApplications.launch(intent);
}
});
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
index c49f4ba75..05c3e2da9 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
@@ -16,7 +16,9 @@
package org.strongswan.android.ui;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -26,6 +28,7 @@ import android.content.SharedPreferences;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.IBinder;
+import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -127,6 +130,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
return mDataSource != null ? mDataSource.getVpnProfile(uuid) : null;
}
+ @SuppressLint("StartActivityAndCollapseDeprecated")
@Override
public void onClick()
{
@@ -177,10 +181,26 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
if (profile.getVpnType().has(VpnType.VpnTypeFeature.USER_PASS) &&
profile.getPassword() == null)
{ /* the user will have to enter the password, so collapse the drawer */
- startActivityAndCollapse(intent);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ {
+ startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE));
+ }
+ else
+ {
+ startActivityAndCollapse(intent);
+ }
}
else
{
+ /* a bug in Android 14+ requires us to request this permission in
+ * order to start the activity from this "background" service */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !Settings.canDrawOverlays(this))
+ {
+ Intent permIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+ permIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityAndCollapse(PendingIntent.getActivity(this, 0, permIntent, PendingIntent.FLAG_IMMUTABLE));
+ return;
+ }
startActivity(intent);
}
return;
@@ -188,7 +208,14 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
}
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityAndCollapse(intent);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ {
+ startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE));
+ }
+ else
+ {
+ startActivityAndCollapse(intent);
+ }
}
@Override
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java
index 59394db16..522d0b1a0 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java
@@ -16,6 +16,8 @@
package org.strongswan.android.ui.widget;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
@@ -33,8 +35,6 @@ import com.google.android.material.textfield.TextInputLayout;
import org.strongswan.android.R;
import androidx.annotation.Nullable;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.ViewPropertyAnimatorListenerAdapter;
/**
* Layout that extends {@link TextInputLayout} with a helper text
@@ -113,8 +113,8 @@ public class TextInputLayoutHelper extends TextInputLayout
showHelper(hasFocus);
}
});
- ViewCompat.setPaddingRelative(mHelperContainer, ViewCompat.getPaddingStart(text),
- 0, ViewCompat.getPaddingEnd(text), text.getPaddingBottom());
+ mHelperContainer.setPaddingRelative(text.getPaddingStart(), 0,
+ text.getPaddingEnd(), text.getPaddingBottom());
}
}
}
@@ -158,27 +158,27 @@ public class TextInputLayoutHelper extends TextInputLayout
}
if (show)
{
- ViewCompat.animate(mHelperContainer)
+ mHelperContainer.animate()
.alpha(1f)
.setDuration(200)
- .setListener(new ViewPropertyAnimatorListenerAdapter() {
+ .setListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(View view)
+ public void onAnimationStart(Animator animation)
{
- view.setVisibility(View.VISIBLE);
+ mHelperContainer.setVisibility(View.VISIBLE);
}
}).start();
}
else
{
- ViewCompat.animate(mHelperContainer)
+ mHelperContainer.animate()
.alpha(0f)
.setDuration(200)
- .setListener(new ViewPropertyAnimatorListenerAdapter() {
+ .setListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(View view)
+ public void onAnimationEnd(Animator animation)
{
- view.setVisibility(View.INVISIBLE);
+ mHelperContainer.setVisibility(View.INVISIBLE);
}
}).start();
}
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 bbaa93aed..5521e02a9 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,3 +1,9 @@
+# 2.5.2 #
+
+- Ziel-SDK auf Android 14 erhöht
+- Wegen eines Bugs in Android 14 ist eine zusätzliche Permission ist nötig, um von der Status-Kachel eine Verbindung im Hintergrund zu starten
+- Fixt einen Crash beim Öffnen der Liste installierter Apps in neuen Profilen
+
# 2.5.1 #
- Fix für existierende Verknüpfungen und Automatisierung via Intents
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 b5179fea9..0dc823127 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,3 +1,9 @@
+# 2.5.2 #
+
+- Increased target SDK to Android 14
+- Due to a bug in Android 14, a new permission is necessary to start a profile in the background from the status tile
+- Fix crash when listing installed apps for new profiles
+
# 2.5.1 #
- Fix for existing shortcuts and automation via Intents
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
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 6a58f62ad..b64f5461c 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
@@ -39,7 +39,7 @@
Unique identifier
Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
- @string/profile_name_label
+ @string/profile_name_label_simple
@string/profile_name_hint
@string/profile_vpn_type_label
The type of client authentication used by the VPN profile
diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle
index 2b505ba48..3fa6c71da 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.2.0'
+ classpath 'com.android.tools.build:gradle:8.5.1'
}
}
diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties
index d219d263c..8c8c77e3f 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.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip