From 44e16a63ef4bd0dc24b1fcecb93e3737ce749fa3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 28 Jun 2021 14:21:41 +0200 Subject: [PATCH 01/15] android: Update Gradle plugin --- src/frontends/android/build.gradle | 2 +- src/frontends/android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle index 3798c323a..58f324c18 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:4.0.1' + classpath 'com.android.tools.build:gradle:4.2.2' } } diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties index 35b130583..65249338f 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-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip From 58310099417f31b4d5cfd43251d5afb72fcaf53e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 28 Jun 2021 14:22:07 +0200 Subject: [PATCH 02/15] android: Update dependencies --- src/frontends/android/app/build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index c0a1c9352..295c8cb7d 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -46,13 +46,13 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'com.google.android.material:material:1.2.1' - testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.28.2' - testImplementation 'org.powermock:powermock-core:2.0.2' - testImplementation 'org.powermock:powermock-module-junit4:2.0.2' - testImplementation 'org.powermock:powermock-api-mockito2:2.0.2' + implementation 'com.google.android.material:material:1.4.0' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:3.11.2' + testImplementation 'org.powermock:powermock-core:2.0.9' + testImplementation 'org.powermock:powermock-module-junit4:2.0.9' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.9' } From 6f3725ea8ba18b287a4405af3b73db02b8b20587 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 28 Jun 2021 14:26:24 +0200 Subject: [PATCH 03/15] android: Replace jcenter with mavenCentral repository --- src/frontends/android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle index 58f324c18..ced60e220 100644 --- a/src/frontends/android/build.gradle +++ b/src/frontends/android/build.gradle @@ -1,6 +1,6 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { @@ -10,7 +10,7 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() google() } } From a885e38265f20514f8ca42e7a3aee2ffc680541b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 28 Jun 2021 14:26:52 +0200 Subject: [PATCH 04/15] android: Set compile-/targetSdkVersion to 30 This will be mandatory for new apps in August and for existing apps in November. However, several classes like AsyncTask are now deprecated so this needs some work to avoid warnings and problems in the future. --- src/frontends/android/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 295c8cb7d..beaeb5a17 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 29 + compileSdkVersion 30 defaultConfig { applicationId "org.strongswan.android" minSdkVersion 15 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 74 versionName "2.3.2" } From 7cd50aeb648d6345fd09adb65f05994fc6a50d62 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 9 Jul 2021 18:18:13 +0200 Subject: [PATCH 05/15] android: Replace deprecated startActivityForResult/onActivityResult usage --- .../ui/TrustedCertificateImportActivity.java | 37 ++++---- .../ui/TrustedCertificatesActivity.java | 33 +++---- .../android/ui/VpnProfileControlActivity.java | 95 ++++++++++--------- .../android/ui/VpnProfileDetailActivity.java | 61 ++++++------ .../android/ui/VpnProfileImportActivity.java | 56 ++++++----- .../android/ui/VpnProfileListFragment.java | 8 +- 6 files changed, 137 insertions(+), 153 deletions(-) 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 0beabd465..44f4f5ff9 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 @@ -16,7 +16,6 @@ package org.strongswan.android.ui; import android.annotation.TargetApi; -import android.app.Activity; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; @@ -37,6 +36,8 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDialogFragment; @@ -44,10 +45,21 @@ import androidx.fragment.app.FragmentTransaction; public class TrustedCertificateImportActivity extends AppCompatActivity { - private static final int OPEN_DOCUMENT = 0; private static final String DIALOG_TAG = "Dialog"; private Uri mCertificateUri; + private final ActivityResultLauncher mOpenDocument = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) + { + mCertificateUri = result.getData().getData(); + return; + } + finish(); + } + ); + @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void onCreate(Bundle savedInstanceState) @@ -71,7 +83,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity openIntent.setType("*/*"); try { - startActivityForResult(openIntent, OPEN_DOCUMENT); + mOpenDocument.launch(openIntent); } catch (ActivityNotFoundException e) { /* some devices are unable to browse for files */ @@ -81,23 +93,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity } } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) - { - case OPEN_DOCUMENT: - if (resultCode == Activity.RESULT_OK && data != null) - { - mCertificateUri = data.getData(); - return; - } - finish(); - return; - } - } - @Override protected void onPostResume() { @@ -214,7 +209,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity if (activity.storeCertificate(certificate)) { Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show(); - getActivity().setResult(Activity.RESULT_OK); + getActivity().setResult(RESULT_OK); } else { 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 1c13a110f..508bc5350 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 @@ -15,7 +15,6 @@ package org.strongswan.android.ui; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Build; @@ -34,6 +33,8 @@ import org.strongswan.android.ui.CertificateDeleteConfirmationDialog.OnCertifica import java.security.KeyStore; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; @@ -45,11 +46,20 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr { public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE"; private static final String DIALOG_TAG = "Dialog"; - private static final int IMPORT_CERTIFICATE = 0; private TrustedCertificatesPagerAdapter mAdapter; private ViewPager mPager; private boolean mSelect; + private final ActivityResultLauncher mImportCertificate = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) + { + reloadCertificates(); + } + } + ); + @Override public void onCreate(Bundle savedInstanceState) { @@ -100,27 +110,12 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr return true; case R.id.menu_import_certificate: Intent intent = new Intent(this, TrustedCertificateImportActivity.class); - startActivityForResult(intent, IMPORT_CERTIFICATE); + mImportCertificate.launch(intent); return true; } return super.onOptionsItemSelected(item); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case IMPORT_CERTIFICATE: - if (resultCode == Activity.RESULT_OK) - { - reloadCertificates(); - } - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - @Override public void onTrustedCertificateSelected(TrustedCertificateEntry selected) { @@ -129,7 +124,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr /* the user selected a certificate, return to calling activity */ Intent intent = new Intent(); intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias()); - setResult(Activity.RESULT_OK, intent); + setResult(RESULT_OK, intent); finish(); } else if (mAdapter.getSource(mPager.getCurrentItem()) == TrustedCertificateSource.LOCAL) 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 5174cafb0..beda1b3ff 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 @@ -44,6 +44,8 @@ import org.strongswan.android.logic.VpnStateService; import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.utils.Constants; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -59,8 +61,6 @@ public class VpnProfileControlActivity extends AppCompatActivity public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT"; public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID"; - private static final int PREPARE_VPN_SERVICE = 0; - private static final int ADD_TO_POWER_WHITELIST = 1; 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"; @@ -87,6 +87,33 @@ public class VpnProfileControlActivity extends AppCompatActivity } }; + private final ActivityResultLauncher mPrepareVpnService = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + mWaitingForResult = false; + if (result.getResultCode() == RESULT_OK && mProfileInfo != null) + { + onVpnServicePrepared(); + } + else + { /* this happens if the always-on VPN feature is activated by a different app or the user declined */ + VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission); + } + } + ); + + private final ActivityResultLauncher mAddToPowerWhitelist = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + mWaitingForResult = false; + if (mProfileInfo != null && mService != null) + { + mService.connect(mProfileInfo, true); + } + finish(); + } + ); + @Override public void onCreate(Bundle savedInstanceState) { @@ -173,7 +200,7 @@ public class VpnProfileControlActivity extends AppCompatActivity try { mWaitingForResult = true; - startActivityForResult(intent, PREPARE_VPN_SERVICE); + mPrepareVpnService.launch(intent); } catch (ActivityNotFoundException ex) { @@ -187,7 +214,23 @@ public class VpnProfileControlActivity extends AppCompatActivity } else { /* user already granted permission to use VpnService */ - onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); + onVpnServicePrepared(); + } + } + + /** + * Called once the VpnService has been prepared and permission has been granted + * by the user. + */ + protected void onVpnServicePrepared() + { + if (checkPowerWhitelist()) + { + if (mService != null) + { + mService.connect(mProfileInfo, true); + } + finish(); } } @@ -219,48 +262,6 @@ public class VpnProfileControlActivity extends AppCompatActivity return true; } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case PREPARE_VPN_SERVICE: - mWaitingForResult = false; - if (resultCode == RESULT_OK && mProfileInfo != null) - { - if (checkPowerWhitelist()) - { - if (mService != null) - { - mService.connect(mProfileInfo, true); - } - finish(); - } - } - else - { /* this happens if the always-on VPN feature is activated by a different app or the user declined */ - if (getSupportFragmentManager().isStateSaved()) - { /* onActivityResult() might be called when we aren't active anymore e.g. if the - * user pressed the home button, if the activity is started again we land here - * before onNewIntent() is called */ - return; - } - VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission); - } - break; - case ADD_TO_POWER_WHITELIST: - mWaitingForResult = false; - if (mProfileInfo != null && mService != null) - { - mService.connect(mProfileInfo, true); - } - finish(); - break; - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - /** * Check if we are currently connected to a VPN connection * @@ -597,7 +598,7 @@ public class VpnProfileControlActivity extends AppCompatActivity activity.mWaitingForResult = true; Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:" + activity.getPackageName())); - activity.startActivityForResult(intent, ADD_TO_POWER_WHITELIST); + activity.mAddToPowerWhitelist.launch(intent); }).create(); } 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 5373c4095..391c3e4db 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 @@ -74,6 +74,8 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDialogFragment; @@ -82,9 +84,6 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; public class VpnProfileDetailActivity extends AppCompatActivity { - private static final int SELECT_TRUSTED_CERTIFICATE = 0; - private static final int SELECT_APPLICATIONS = 1; - private VpnProfileDataSource mDataSource; private Long mId; private TrustedCertificateEntry mCertEntry; @@ -144,6 +143,31 @@ public class VpnProfileDetailActivity extends AppCompatActivity private EditText mDnsServers; private TextInputLayoutHelper mDnsServersWrap; + private final ActivityResultLauncher mSelectTrustedCertificate = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) + { + String alias = result.getData().getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE); + X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias); + mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate); + updateCertificateSelector(); + } + } + ); + + private final ActivityResultLauncher mSelectApplications = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) + { + ArrayList selection = result.getData().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST); + mSelectedApps = new TreeSet<>(selection); + updateAppsSelector(); + } + } + ); + @Override public void onCreate(Bundle savedInstanceState) { @@ -300,7 +324,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity { Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class); intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE); - startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE); + mSelectTrustedCertificate.launch(intent); } }); @@ -334,7 +358,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity { Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class); intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps)); - startActivityForResult(intent, SELECT_APPLICATIONS); + mSelectApplications.launch(intent); } }); @@ -404,33 +428,6 @@ public class VpnProfileDetailActivity extends AppCompatActivity } } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case SELECT_TRUSTED_CERTIFICATE: - if (resultCode == RESULT_OK) - { - String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE); - X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias); - mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate); - updateCertificateSelector(); - } - break; - case SELECT_APPLICATIONS: - if (resultCode == RESULT_OK) - { - ArrayList selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST); - mSelectedApps = new TreeSet<>(selection); - updateAppsSelector(); - } - break; - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - /** * Update the UI to enter credentials depending on the type of VPN currently selected */ 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 12e77eeb2..84fb584a1 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 @@ -15,7 +15,6 @@ package org.strongswan.android.ui; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; @@ -74,6 +73,8 @@ import java.util.UUID; import javax.net.ssl.SSLHandshakeException; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; import androidx.loader.app.LoaderManager; import androidx.loader.content.AsyncTaskLoader; @@ -84,8 +85,6 @@ public class VpnProfileImportActivity extends AppCompatActivity { private static final String PKCS12_INSTALLED = "PKCS12_INSTALLED"; private static final String PROFILE_URI = "PROFILE_URI"; - private static final int INSTALL_PKCS12 = 0; - private static final int OPEN_DOCUMENT = 1; private static final int PROFILE_LOADER = 0; private static final int USER_CERT_LOADER = 1; @@ -112,6 +111,29 @@ public class VpnProfileImportActivity extends AppCompatActivity private ViewGroup mRemoteCertificate; private RelativeLayout mRemoteCert; + private final ActivityResultLauncher mImportPKCS12 = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) + { /* no need to import twice */ + mImportUserCert.setEnabled(false); + mSelectUserCert.performClick(); + } + } + ); + + private final ActivityResultLauncher mOpenDocument = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) + { + loadProfile(result.getData().getData()); + return; + } + finish(); + } + ); + private LoaderManager.LoaderCallbacks mProfileLoaderCallbacks = new LoaderManager.LoaderCallbacks() { @Override @@ -200,7 +222,7 @@ public class VpnProfileImportActivity extends AppCompatActivity Intent intent = KeyChain.createInstallIntent(); intent.putExtra(KeyChain.EXTRA_NAME, getString(R.string.profile_cert_alias, mProfile.getName())); intent.putExtra(KeyChain.EXTRA_PKCS12, mProfile.PKCS12); - startActivityForResult(intent, INSTALL_PKCS12); + mImportPKCS12.launch(intent); } }); @@ -216,7 +238,7 @@ public class VpnProfileImportActivity extends AppCompatActivity openIntent.setType("*/*"); try { - startActivityForResult(openIntent, OPEN_DOCUMENT); + mOpenDocument.launch(openIntent); } catch (ActivityNotFoundException e) { /* some devices are unable to browse for files */ @@ -283,30 +305,6 @@ public class VpnProfileImportActivity extends AppCompatActivity } } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) - { - case INSTALL_PKCS12: - if (resultCode == Activity.RESULT_OK) - { /* no need to import twice */ - mImportUserCert.setEnabled(false); - mSelectUserCert.performClick(); - } - break; - case OPEN_DOCUMENT: - if (resultCode == Activity.RESULT_OK && data != null) - { - loadProfile(data.getData()); - return; - } - finish(); - break; - } - } - private void loadProfile(Uri uri) { mProgressBar.show(); 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 c6d430f36..4203eade2 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 @@ -55,8 +55,6 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; public class VpnProfileListFragment extends Fragment { private static final String SELECTED_KEY = "SELECTED"; - private static final int ADD_REQUEST = 1; - private static final int EDIT_REQUEST = 2; private List mVpnProfiles; private VpnProfileDataSource mDataSource; @@ -212,7 +210,7 @@ public class VpnProfileListFragment extends Fragment case R.id.add_profile: Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); - startActivityForResult(connectionIntent, ADD_REQUEST); + startActivity(connectionIntent); return true; default: return super.onOptionsItemSelected(item); @@ -270,7 +268,7 @@ public class VpnProfileListFragment extends Fragment VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); - startActivityForResult(connectionIntent, EDIT_REQUEST); + startActivity(connectionIntent); break; } case R.id.copy_profile: @@ -288,7 +286,7 @@ public class VpnProfileListFragment extends Fragment Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); - startActivityForResult(connectionIntent, EDIT_REQUEST); + startActivity(connectionIntent); break; } case R.id.delete_profile: From 93c494e295e0fd7c6798ffbe2f0e6a2857177c5f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 9 Jul 2021 18:23:05 +0200 Subject: [PATCH 06/15] android: Replace deprecated onActivityCreated() with onViewCreated() --- .../android/ui/RemediationInstructionFragment.java | 6 ++++-- .../android/ui/RemediationInstructionsFragment.java | 6 ++++-- .../android/ui/SelectedApplicationsListFragment.java | 5 +++-- .../android/ui/TrustedCertificateListFragment.java | 6 ++++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java index aa1f4729a..52d9959b0 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java @@ -26,6 +26,8 @@ import android.widget.TextView; import org.strongswan.android.R; import org.strongswan.android.logic.imc.RemediationInstruction; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; public class RemediationInstructionFragment extends ListFragment @@ -49,9 +51,9 @@ public class RemediationInstructionFragment extends ListFragment } @Override - public void onActivityCreated(Bundle savedInstanceState) + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + super.onViewCreated(view, savedInstanceState); if (savedInstanceState != null) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java index 636d88e16..1a9352be3 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java @@ -26,6 +26,8 @@ import org.strongswan.android.ui.adapter.RemediationInstructionAdapter; import java.util.ArrayList; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; public class RemediationInstructionsFragment extends ListFragment @@ -46,9 +48,9 @@ public class RemediationInstructionsFragment extends ListFragment } @Override - public void onActivityCreated(Bundle savedInstanceState) + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + super.onViewCreated(view, savedInstanceState); if (savedInstanceState != null) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java index 2b041efa7..8fc121d51 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.SearchView; import androidx.fragment.app.ListFragment; @@ -53,9 +54,9 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo private SortedSet mSelection; @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + super.onViewCreated(view, savedInstanceState); setHasOptionsMenu(true); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 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 b89386a86..eaf32a0e3 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 @@ -41,6 +41,8 @@ import java.util.Map.Entry; import java.util.Observable; import java.util.Observer; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.ListFragment; import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager.LoaderCallbacks; @@ -63,9 +65,9 @@ public class TrustedCertificateListFragment extends ListFragment implements Load } @Override - public void onActivityCreated(Bundle savedInstanceState) + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + super.onViewCreated(view, savedInstanceState); setHasOptionsMenu(true); setEmptyText(getString(R.string.no_certificates)); From dc351a30e1692a73b4c331917fd7c529a39f6bc5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 12 Jul 2021 15:38:22 +0200 Subject: [PATCH 07/15] android: Replace usage of deprecated Handler() constructor --- .../java/org/strongswan/android/logic/CharonVpnService.java | 2 +- .../java/org/strongswan/android/logic/VpnStateService.java | 6 ++++-- .../main/java/org/strongswan/android/ui/LogFragment.java | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) 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 5a37955d3..bba5a427f 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 @@ -192,7 +192,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe mAppDir = getFilesDir().getAbsolutePath(); /* handler used to do changes in the main UI thread */ - mHandler = new Handler(); + mHandler = new Handler(getMainLooper()); mDataSource = new VpnProfileDataSource(this); mDataSource.open(); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java index 551aff60b..a10a0993f 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java @@ -22,6 +22,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.SystemClock; @@ -107,7 +108,7 @@ public class VpnStateService extends Service { /* this handler allows us to notify listeners from the UI thread and * not from the threads that actually report any state changes */ - mHandler = new RetryHandler(this); + mHandler = new RetryHandler(getMainLooper(), this); } @Override @@ -536,8 +537,9 @@ public class VpnStateService extends Service private static class RetryHandler extends Handler { WeakReference mService; - public RetryHandler(VpnStateService service) + public RetryHandler(Looper looper, VpnStateService service) { + super(looper); mService = new WeakReference<>(service); } 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 2882740d5..f6052f62f 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 @@ -19,6 +19,7 @@ import android.content.Context; import android.os.Bundle; import android.os.FileObserver; import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -55,7 +56,7 @@ public class LogFragment extends Fragment mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; - mLogHandler = new Handler(); + mLogHandler = new Handler(Looper.getMainLooper()); mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath()); } From c976165533f2619afe0a97c314643c593dfbb4ad Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 12 Jul 2021 15:53:42 +0200 Subject: [PATCH 08/15] android: Replace deprecated FileObserver() constructor with newer SDKs --- .../strongswan/android/ui/LogFragment.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) 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 f6052f62f..52f093c39 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 @@ -16,6 +16,7 @@ package org.strongswan.android.ui; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.os.FileObserver; import android.os.Handler; @@ -37,6 +38,7 @@ import java.io.StringReader; import java.util.ArrayList; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; public class LogFragment extends Fragment @@ -58,7 +60,15 @@ public class LogFragment extends Fragment mLogHandler = new Handler(Looper.getMainLooper()); - mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath()); + File logdir = getActivity().getFilesDir(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + { + mDirectoryObserver = new LogDirectoryObserver(logdir); + } + else + { + mDirectoryObserver = new LogDirectoryObserver(logdir.getAbsolutePath()); + } } @Override @@ -224,14 +234,20 @@ public class LogFragment extends Fragment */ private class LogDirectoryObserver extends FileObserver { - private final File mFile; - private long mSize; + private static final int mMask = FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE; + private final File mFile = new File(mLogFilePath); + private long mSize = mFile.length(); + @SuppressWarnings("deprecation") public LogDirectoryObserver(String path) { - super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE); - mFile = new File(mLogFilePath); - mSize = mFile.length(); + super(path, mMask); + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + public LogDirectoryObserver(File path) + { + super(path, mMask); } @Override From a8cc1462407a792e8e6bc857a892394929416afb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 12 Jul 2021 17:17:00 +0200 Subject: [PATCH 09/15] android: Migrate from deprecated ViewPager to ViewPager2 for CA cert lists --- .../ui/TrustedCertificatesActivity.java | 49 ++++++++++--------- .../layout/trusted_certificates_activity.xml | 2 +- 2 files changed, 27 insertions(+), 24 deletions(-) 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 508bc5350..ad665787b 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 @@ -15,7 +15,6 @@ package org.strongswan.android.ui; -import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; @@ -23,6 +22,7 @@ import android.view.Menu; import android.view.MenuItem; import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfileDataSource; @@ -35,19 +35,20 @@ import java.security.KeyStore; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.viewpager.widget.ViewPager; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; public class TrustedCertificatesActivity extends AppCompatActivity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener, OnCertificateDeleteListener { public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE"; private static final String DIALOG_TAG = "Dialog"; private TrustedCertificatesPagerAdapter mAdapter; - private ViewPager mPager; + private ViewPager2 mPager; private boolean mSelect; private final ActivityResultLauncher mImportCertificate = registerForActivityResult( @@ -69,13 +70,15 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); - mAdapter = new TrustedCertificatesPagerAdapter(getSupportFragmentManager(), this); + mAdapter = new TrustedCertificatesPagerAdapter(this); - mPager = (ViewPager)findViewById(R.id.viewpager); + mPager = (ViewPager2)findViewById(R.id.viewpager); mPager.setAdapter(mAdapter); TabLayout tabs = (TabLayout)findViewById(R.id.tabs); - tabs.setupWithViewPager(mPager); + new TabLayoutMediator(tabs, mPager, (tab, position) -> { + tab.setText(mAdapter.getTitle(position)); + }).attach(); mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction()); } @@ -158,28 +161,21 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr TrustedCertificateManager.getInstance().reset(); } - public static class TrustedCertificatesPagerAdapter extends FragmentPagerAdapter + public static class TrustedCertificatesPagerAdapter extends FragmentStateAdapter { private TrustedCertificatesTab mTabs[]; - public TrustedCertificatesPagerAdapter(FragmentManager fm, Context context) + public TrustedCertificatesPagerAdapter(@NonNull FragmentActivity fragmentActivity) { - super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + super(fragmentActivity); mTabs = new TrustedCertificatesTab[]{ - new TrustedCertificatesTab(context.getString(R.string.system_tab), TrustedCertificateSource.SYSTEM), - new TrustedCertificatesTab(context.getString(R.string.user_tab), TrustedCertificateSource.USER), - new TrustedCertificatesTab(context.getString(R.string.local_tab), TrustedCertificateSource.LOCAL), + new TrustedCertificatesTab(fragmentActivity.getString(R.string.system_tab), TrustedCertificateSource.SYSTEM), + new TrustedCertificatesTab(fragmentActivity.getString(R.string.user_tab), TrustedCertificateSource.USER), + new TrustedCertificatesTab(fragmentActivity.getString(R.string.local_tab), TrustedCertificateSource.LOCAL), }; } - @Override - public int getCount() - { - return mTabs.length; - } - - @Override - public CharSequence getPageTitle(int position) + public CharSequence getTitle(int position) { return mTabs[position].getTitle(); } @@ -190,7 +186,14 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr } @Override - public Fragment getItem(int position) + public int getItemCount() + { + return mTabs.length; + } + + @NonNull + @Override + public Fragment createFragment(int position) { TrustedCertificateListFragment fragment = new TrustedCertificateListFragment(); Bundle args = new Bundle(); 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 44b45e628..f560c0a08 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 @@ -26,7 +26,7 @@ app:tabGravity="fill" app:tabMode="fixed"/> - Date: Mon, 12 Jul 2021 17:58:16 +0200 Subject: [PATCH 10/15] android: Replace deprecated AsyncTask instances As suggested by the Android docs, we use a global thread pool and handler to avoid recreating them repeatedly. Four threads should be more than enough as we only use this to load CA certificates when the app starts initially and to load user certs when editing a profile. --- .../android/logic/StrongSwanApplication.java | 27 ++++++ .../strongswan/android/ui/MainActivity.java | 20 ++--- .../android/ui/VpnProfileDetailActivity.java | 89 +++++++++++-------- 3 files changed, 83 insertions(+), 53 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java index a868b72cd..505c00cc9 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java @@ -16,6 +16,9 @@ package org.strongswan.android.logic; import java.security.Security; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.strongswan.android.security.LocalCertificateKeyStoreProvider; import org.strongswan.android.ui.MainActivity; @@ -23,10 +26,16 @@ import org.strongswan.android.ui.MainActivity; import android.app.Application; import android.content.Context; import android.os.Build; +import android.os.Handler; +import android.os.Looper; + +import androidx.core.os.HandlerCompat; public class StrongSwanApplication extends Application { private static Context mContext; + private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4); + private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper()); static { Security.addProvider(new LocalCertificateKeyStoreProvider()); @@ -48,6 +57,24 @@ public class StrongSwanApplication extends Application return StrongSwanApplication.mContext; } + /** + * Returns a thread pool to run tasks in separate threads + * @return thread pool + */ + public Executor getExecutor() + { + return mExecutorService; + } + + /** + * Returns a handler to execute stuff by the main thread. + * @return handler + */ + public Handler getHandler() + { + return mMainHandler; + } + /* * The libraries are extracted to /data/data/org.strongswan.android/... * during installation. On newer releases most are loaded in JNI_OnLoad. 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 18953d185..9b47b7f21 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 @@ -20,7 +20,6 @@ package org.strongswan.android.ui; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.text.format.Formatter; @@ -30,6 +29,7 @@ import android.widget.Toast; import org.strongswan.android.R; 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; @@ -68,8 +68,10 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec bar.setDisplayShowTitleEnabled(false); bar.setIcon(R.mipmap.ic_app); - /* load CA certificates in a background task */ - new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + /* load CA certificates in a background thread */ + ((StrongSwanApplication)getApplication()).getExecutor().execute(() -> { + TrustedCertificateManager.getInstance().load(); + }); } @Override @@ -157,18 +159,6 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec dialog.show(this.getSupportFragmentManager(), DIALOG_TAG); } - /** - * Class that loads the cached CA certificates. - */ - private class LoadCertificatesTask extends AsyncTask - { - @Override - protected TrustedCertificateManager doInBackground(Void... params) - { - return TrustedCertificateManager.getInstance().load(); - } - } - /** * Dismiss dialog if shown */ 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 391c3e4db..65d97ba21 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 @@ -21,9 +21,9 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.security.KeyChain; import android.security.KeyChainAliasCallback; import android.security.KeyChainException; @@ -59,6 +59,7 @@ import org.strongswan.android.data.VpnProfile.SelectedAppsHandling; import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.data.VpnType; import org.strongswan.android.data.VpnType.VpnTypeFeature; +import org.strongswan.android.logic.StrongSwanApplication; import org.strongswan.android.logic.TrustedCertificateManager; import org.strongswan.android.security.TrustedCertificateEntry; import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter; @@ -73,6 +74,7 @@ import java.util.ArrayList; import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; +import java.util.concurrent.Executor; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -788,9 +790,22 @@ public class VpnProfileDetailActivity extends AppCompatActivity useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); if (useralias != null) { - UserCertificateLoader loader = new UserCertificateLoader(this, useralias); mUserCertLoading = useralias; - loader.execute(); + UserCertificateLoader loader = new UserCertificateLoader(((StrongSwanApplication)getApplication()).getExecutor(), + ((StrongSwanApplication)getApplication()).getHandler()); + loader.loadCertifiate(this, useralias, result -> { + if (result != null) + { + mUserCertEntry = new TrustedCertificateEntry(mUserCertLoading, result); + } + else + { /* previously selected certificate is not here anymore */ + ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(""); + mUserCertEntry = null; + } + mUserCertLoading = null; + updateCredentialView(); + }); } /* check if the user selected a CA certificate previously */ @@ -958,55 +973,53 @@ public class VpnProfileDetailActivity extends AppCompatActivity } } + /** + * Callback interface for the user certificate loader. + */ + private interface UserCertificateLoaderCallback { + void onComplete(X509Certificate result); + } + /** * Load the selected user certificate asynchronously. This cannot be done * from the main thread as getCertificateChain() calls back to our main * thread to bind to the KeyChain service resulting in a deadlock. */ - private class UserCertificateLoader extends AsyncTask + private class UserCertificateLoader { - private final Context mContext; - private final String mAlias; + private final Executor mExecutor; + private final Handler mHandler; - public UserCertificateLoader(Context context, String alias) + public UserCertificateLoader(Executor executor, Handler handler) { - mContext = context; - mAlias = alias; + mExecutor = executor; + mHandler = handler; } - @Override - protected X509Certificate doInBackground(Void... params) + public void loadCertifiate(Context context, String alias, UserCertificateLoaderCallback callback) { - X509Certificate[] chain = null; - try - { - chain = KeyChain.getCertificateChain(mContext, mAlias); - } - catch (KeyChainException | InterruptedException e) - { - e.printStackTrace(); - } - if (chain != null && chain.length > 0) - { - return chain[0]; - } - return null; + mExecutor.execute(() -> { + X509Certificate[] chain = null; + try + { + chain = KeyChain.getCertificateChain(context, alias); + } + catch (KeyChainException | InterruptedException e) + { + e.printStackTrace(); + } + if (chain != null && chain.length > 0) + { + complete(chain[0], callback); + return; + } + complete(null, callback); + }); } - @Override - protected void onPostExecute(X509Certificate result) + protected void complete(X509Certificate result, UserCertificateLoaderCallback callback) { - if (result != null) - { - mUserCertEntry = new TrustedCertificateEntry(mAlias, result); - } - else - { /* previously selected certificate is not here anymore */ - ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(""); - mUserCertEntry = null; - } - mUserCertLoading = null; - updateCredentialView(); + mHandler.post(() -> callback.onComplete(result)); } } From b3cdbe669314a9117bf0a8b45d06b8ad86e1aed0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 13 Jul 2021 15:11:07 +0200 Subject: [PATCH 11/15] android: Replace deprecated Switch with SwitchCompat --- .../android/ui/VpnProfileDetailActivity.java | 16 ++++++++-------- .../src/main/res/layout/profile_detail_view.xml | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) 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 65d97ba21..89e999ef7 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 @@ -50,7 +50,6 @@ import android.widget.EditText; import android.widget.MultiAutoCompleteTextView; import android.widget.RelativeLayout; import android.widget.Spinner; -import android.widget.Switch; import android.widget.TextView; import org.strongswan.android.R; @@ -81,6 +80,7 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDialogFragment; +import androidx.appcompat.widget.SwitchCompat; import androidx.core.text.HtmlCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -120,12 +120,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity private TextInputLayoutHelper mMTUWrap; private EditText mPort; private TextInputLayoutHelper mPortWrap; - private Switch mCertReq; - private Switch mUseCrl; - private Switch mUseOcsp; - private Switch mStrictRevocation; - private Switch mRsaPss; - private Switch mIPv6Transport; + private SwitchCompat mCertReq; + private SwitchCompat mUseCrl; + private SwitchCompat mUseOcsp; + private SwitchCompat mStrictRevocation; + private SwitchCompat mRsaPss; + private SwitchCompat mIPv6Transport; private EditText mNATKeepalive; private TextInputLayoutHelper mNATKeepaliveWrap; private EditText mIncludedSubnets; @@ -216,7 +216,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap); mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive); mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap); - mCertReq = (Switch)findViewById(R.id.cert_req); + mCertReq = findViewById(R.id.cert_req); mUseCrl = findViewById(R.id.use_crl); mUseOcsp = findViewById(R.id.use_ocsp); mStrictRevocation= findViewById(R.id.strict_revocation); diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index d5935b42c..22847e001 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -280,7 +280,7 @@ - - - - - - Date: Mon, 12 Jul 2021 18:58:58 +0200 Subject: [PATCH 12/15] android: Add a button to install user certificates Newer Android versions don't provide this option anymore on the selection dialog. --- .../android/ui/VpnProfileDetailActivity.java | 15 +++++++++++++++ .../src/main/res/layout/profile_detail_view.xml | 8 ++++++++ .../app/src/main/res/values-de/strings.xml | 1 + .../app/src/main/res/values-pl/strings.xml | 1 + .../app/src/main/res/values-ru/strings.xml | 1 + .../app/src/main/res/values-ua/strings.xml | 1 + .../app/src/main/res/values-zh-rCN/strings.xml | 1 + .../app/src/main/res/values-zh-rTW/strings.xml | 1 + .../android/app/src/main/res/values/strings.xml | 1 + 9 files changed, 30 insertions(+) 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 89e999ef7..852e09f7c 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 @@ -43,6 +43,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -145,6 +146,16 @@ public class VpnProfileDetailActivity extends AppCompatActivity private EditText mDnsServers; private TextInputLayoutHelper mDnsServersWrap; + private final ActivityResultLauncher mInstallPKCS12 = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) + { + mSelectUserCert.performClick(); + } + } + ); + private final ActivityResultLauncher mSelectTrustedCertificate = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -309,6 +320,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity }); mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); + ((Button)findViewById(R.id.install_user_certificate)).setOnClickListener(v -> { + Intent intent = KeyChain.createInstallIntent(); + mInstallPKCS12.launch(intent); + }); mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this); mLocalId.setAdapter(mSelectUserIdAdapter); diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index 22847e001..22fdfc2c7 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -123,6 +123,14 @@ android:id="@+id/select_user_certificate" layout="@layout/two_line_button" /> +