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)); } }