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