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.
This commit is contained in:
Tobias Brunner 2021-07-12 17:58:16 +02:00
parent a8cc146240
commit 26354d0aba
3 changed files with 83 additions and 53 deletions

View File

@ -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.

View File

@ -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<Void, Void, TrustedCertificateManager>
{
@Override
protected TrustedCertificateManager doInBackground(Void... params)
{
return TrustedCertificateManager.getInstance().load();
}
}
/**
* Dismiss dialog if shown
*/

View File

@ -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<Void, Void, X509Certificate>
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));
}
}