Dialer: Replace AsyncTask(Loader)s

They are deprecated

Change-Id: Ie927304e66edab39465850954fc380e8c0c4cfed
diff --git a/java/com/android/contacts/common/dialog/ClearFrequentsDialog.java b/java/com/android/contacts/common/dialog/ClearFrequentsDialog.java
index c320341..ecea6ce 100644
--- a/java/com/android/contacts/common/dialog/ClearFrequentsDialog.java
+++ b/java/com/android/contacts/common/dialog/ClearFrequentsDialog.java
@@ -21,19 +21,22 @@
 import android.app.ProgressDialog;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
-import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.ContactsContract;
 
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.FragmentManager;
 
-import com.android.dialer.contacts.resources.R;
+import com.android.dialer.R;
 import com.android.dialer.util.PermissionsUtil;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 /** Dialog that clears the frequently contacted list after confirming with the user. */
 public class ClearFrequentsDialog extends DialogFragment {
 
@@ -47,39 +50,27 @@
   public Dialog onCreateDialog(Bundle savedInstanceState) {
     final Context context = getActivity().getApplicationContext();
     final ContentResolver resolver = getActivity().getContentResolver();
-    final OnClickListener okListener =
-        new OnClickListener() {
-          @Override
-          public void onClick(DialogInterface dialog, int which) {
-            if (!PermissionsUtil.hasContactsReadPermissions(context)) {
-              return;
-            }
+    final OnClickListener okListener = (dialog, which) -> {
+        if (!PermissionsUtil.hasContactsReadPermissions(context)) {
+            return;
+        }
 
-            final ProgressDialog progressDialog =
-                ProgressDialog.show(
-                    getContext(),
-                    getString(R.string.clearFrequentsProgress_title),
-                    null,
-                    true,
-                    true);
+        final ProgressDialog progressDialog =
+          ProgressDialog.show(
+              getContext(),
+              getString(R.string.clearFrequentsProgress_title),
+              null,
+              true,
+              true);
 
-            final AsyncTask<Void, Void, Void> task =
-                new AsyncTask<Void, Void, Void>() {
-                  @Override
-                  protected Void doInBackground(Void... params) {
-                    resolver.delete(
-                        ContactsContract.DataUsageFeedback.DELETE_USAGE_URI, null, null);
-                    return null;
-                  }
+        final ExecutorService executorService = Executors.newSingleThreadExecutor();
+        final Handler handler = new Handler(Looper.getMainLooper());
 
-                  @Override
-                  protected void onPostExecute(Void result) {
-                    progressDialog.dismiss();
-                  }
-                };
-            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-          }
-        };
+        executorService.execute(() -> {
+          resolver.delete(ContactsContract.DataUsageFeedback.DELETE_USAGE_URI, null, null);
+          handler.post(progressDialog::dismiss);
+        });
+    };
     return new AlertDialog.Builder(getActivity())
         .setTitle(R.string.clearFrequentsConfirmation_title)
         .setMessage(R.string.clearFrequentsConfirmation)
diff --git a/java/com/android/contacts/common/model/AccountTypeManager.java b/java/com/android/contacts/common/model/AccountTypeManager.java
index 70800ae..ee23534 100644
--- a/java/com/android/contacts/common/model/AccountTypeManager.java
+++ b/java/com/android/contacts/common/model/AccountTypeManager.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2023 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +31,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -63,6 +63,8 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -775,18 +777,20 @@
    * the list of all potential invitable account types. Once the work is completed, the list of
    * account types is stored in the {@link AccountTypeManager}'s {@link InvitableAccountTypeCache}.
    */
-  private class FindInvitablesTask
-      extends AsyncTask<Void, Void, Map<AccountTypeWithDataSet, AccountType>> {
+  private class FindInvitablesTask {
 
-    @Override
-    protected Map<AccountTypeWithDataSet, AccountType> doInBackground(Void... params) {
-      return findUsableInvitableAccountTypes(mContext);
-    }
+    private void execute() {
+      ExecutorService executor = Executors.newSingleThreadExecutor();
+      Handler handler = new Handler(Looper.getMainLooper());
 
-    @Override
-    protected void onPostExecute(Map<AccountTypeWithDataSet, AccountType> accountTypes) {
-      mInvitableAccountTypeCache.setCachedValue(accountTypes);
-      mInvitablesTaskIsRunning.set(false);
+      executor.execute(() -> {
+        final Map<AccountTypeWithDataSet, AccountType> accountTypes =
+                findUsableInvitableAccountTypes(mContext);
+        handler.post(() -> {
+          mInvitableAccountTypeCache.setCachedValue(accountTypes);
+          mInvitablesTaskIsRunning.set(false);
+        });
+      });
     }
   }
 }
diff --git a/java/com/android/contacts/common/model/ContactLoader.java b/java/com/android/contacts/common/model/ContactLoader.java
index a3a3b9d..b644ccf 100644
--- a/java/com/android/contacts/common/model/ContactLoader.java
+++ b/java/com/android/contacts/common/model/ContactLoader.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2023 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,7 +17,6 @@
 
 package com.android.contacts.common.model;
 
-import android.content.AsyncTaskLoader;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -36,6 +36,9 @@
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
+
+import androidx.loader.content.AsyncTaskLoader;
+
 import com.android.contacts.common.GroupMetaData;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountTypeWithDataSet;
@@ -53,6 +56,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -64,6 +68,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java
index 53523bd..09f3b96 100644
--- a/java/com/android/dialer/app/calllog/CallLogAdapter.java
+++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java
@@ -24,8 +24,9 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Trace;
 import android.provider.BlockedNumberContract;
 import android.provider.CallLog;
@@ -85,6 +86,8 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /** Adapter class to fill in data for the Call Log. */
 public class CallLogAdapter extends GroupingListAdapter
@@ -121,7 +124,6 @@
   /** Helper to group call log entries. */
   private final CallLogGroupBuilder callLogGroupBuilder;
 
-  private final AsyncTaskExecutor asyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
   private ContactInfoCache contactInfoCache;
   // Tracks the position of the currently expanded list item.
   private int currentlyExpandedPosition = RecyclerView.NO_POSITION;
@@ -665,7 +667,7 @@
       updateCheckMarkedStatusOfEntry(views);
 
       if (views.asyncTask != null) {
-        views.asyncTask.cancel(true);
+        views.asyncTask.cancel();
       }
     }
   }
@@ -746,38 +748,68 @@
     viewHolder.isBlocked = false;
 
     viewHolder.setDetailedPhoneDetails(callDetailsEntries);
-    final AsyncTask<Void, Void, Boolean> loadDataTask =
-        new AsyncTask<Void, Void, Boolean>() {
-          @Override
-          protected Boolean doInBackground(Void... params) {
-            viewHolder.isBlocked = BlockedNumberContract.canCurrentUserBlockNumbers(activity) &&
-                    BlockedNumberContract.isBlocked(activity, viewHolder.number);
-            details.isBlocked = viewHolder.isBlocked;
-            if (isCancelled()) {
-              return false;
-            }
-            return !isCancelled() && loadData(viewHolder, rowId, details);
-          }
-
-          @Override
-          protected void onPostExecute(Boolean success) {
-            viewHolder.isLoaded = true;
-            if (success) {
-              viewHolder.callbackAction = getCallbackAction(viewHolder.rowId);
-              int currentDayGroup = getDayGroup(viewHolder.rowId);
-              if (currentDayGroup != details.previousGroup) {
-                viewHolder.dayGroupHeaderVisibility = View.VISIBLE;
-                viewHolder.dayGroupHeaderText = getGroupDescription(currentDayGroup);
-              } else {
-                viewHolder.dayGroupHeaderVisibility = View.GONE;
-              }
-              render(viewHolder, details, rowId);
-            }
-          }
-        };
+    final LoadDataTask loadDataTask = new LoadDataTask(viewHolder, details, rowId);
 
     viewHolder.asyncTask = loadDataTask;
-    asyncTaskExecutor.submit(LOAD_DATA_TASK_IDENTIFIER, loadDataTask);
+    loadDataTask.execute();
+  }
+
+  public interface LoadDataTaskInterface {
+     void execute();
+     void cancel();
+  }
+
+  private class LoadDataTask implements LoadDataTaskInterface {
+
+    private boolean mIsCancelled = false;
+    private final CallLogListItemViewHolder mViewHolder;
+    private final PhoneCallDetails mDetails;
+    private final long mRowId;
+
+    private final ExecutorService mExecutor;
+    private final Handler mHandler;
+
+    public LoadDataTask(CallLogListItemViewHolder viewHolder, PhoneCallDetails details,
+                        long rowId) {
+      mExecutor = Executors.newSingleThreadExecutor();
+      mHandler = new Handler(Looper.getMainLooper());
+      mViewHolder = viewHolder;
+      mDetails = details;
+      mRowId = rowId;
+    }
+
+    public void execute() {
+      mExecutor.execute(() -> {
+        final boolean success;
+        mViewHolder.isBlocked = BlockedNumberContract.canCurrentUserBlockNumbers(activity) &&
+                BlockedNumberContract.isBlocked(activity, mViewHolder.number);
+        mDetails.isBlocked = mViewHolder.isBlocked;
+        if (mIsCancelled) {
+          success = false;
+        } else {
+          success = !mIsCancelled && loadData(mViewHolder, mRowId, mDetails);
+        }
+
+        mHandler.post(() -> {
+          mViewHolder.isLoaded = true;
+          if (success) {
+            mViewHolder.callbackAction = getCallbackAction(mViewHolder.rowId);
+            int currentDayGroup = getDayGroup(mViewHolder.rowId);
+            if (currentDayGroup != mDetails.previousGroup) {
+              mViewHolder.dayGroupHeaderVisibility = View.VISIBLE;
+              mViewHolder.dayGroupHeaderText = getGroupDescription(currentDayGroup);
+            } else {
+              mViewHolder.dayGroupHeaderVisibility = View.GONE;
+            }
+            render(mViewHolder, mDetails, mRowId);
+          }
+        });
+      });
+    }
+
+    public void cancel() {
+      mIsCancelled = true;
+    }
   }
 
   /**
diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
index d285848..3aebbe9 100644
--- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
+++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2023 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,7 +21,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.provider.CallLog;
 import android.provider.VoicemailContract.Voicemails;
 import android.text.TextUtils;
@@ -52,23 +52,18 @@
     }
 
     asyncTaskExecutor.submit(
-        Tasks.MARK_VOICEMAIL_READ,
-        new AsyncTask<Void, Void, Void>() {
-          @Override
-          public Void doInBackground(Void... params) {
-            ContentValues values = new ContentValues();
-            values.put(Voicemails.IS_READ, true);
-            // "External" changes to the database will be automatically marked as dirty, but this
-            // voicemail might be from dialer so it need to be marked manually.
-            values.put(Voicemails.DIRTY, 1);
-            if (context
-                    .getContentResolver()
-                    .update(voicemailUri, values, Voicemails.IS_READ + " = 0", null)
-                > 0) {
-              uploadVoicemailLocalChangesToServer(context);
-              CallLogNotificationsService.markAllNewVoicemailsAsOld(context);
-            }
-            return null;
+        Tasks.MARK_VOICEMAIL_READ, () -> {
+          ContentValues values = new ContentValues();
+          values.put(Voicemails.IS_READ, true);
+          // "External" changes to the database will be automatically marked as dirty, but this
+          // voicemail might be from dialer so it need to be marked manually.
+          values.put(Voicemails.DIRTY, 1);
+          if (context
+                  .getContentResolver()
+                  .update(voicemailUri, values, Voicemails.IS_READ + " = 0", null)
+              > 0) {
+            uploadVoicemailLocalChangesToServer(context);
+            CallLogNotificationsService.markAllNewVoicemailsAsOld(context);
           }
         });
   }
@@ -81,22 +76,13 @@
       initTaskExecutor();
     }
 
-    asyncTaskExecutor.submit(
-        Tasks.DELETE_VOICEMAIL,
-        new AsyncTask<Void, Void, Void>() {
-          @Override
-          public Void doInBackground(Void... params) {
-            deleteVoicemailSynchronous(context, voicemailUri);
-            return null;
-          }
-
-          @Override
-          public void onPostExecute(Void result) {
-            if (callLogAsyncTaskListener != null) {
-              callLogAsyncTaskListener.onDeleteVoicemail();
-            }
-          }
-        });
+    asyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL,
+            () -> deleteVoicemailSynchronous(context, voicemailUri),
+            () -> {
+      if (callLogAsyncTaskListener != null) {
+        callLogAsyncTaskListener.onDeleteVoicemail();
+      }
+    });
   }
 
   public static void deleteVoicemailSynchronous(Context context, Uri voicemailUri) {
@@ -117,35 +103,28 @@
       initTaskExecutor();
     }
 
-    asyncTaskExecutor.submit(
-        Tasks.MARK_CALL_READ,
-        new AsyncTask<Void, Void, Void>() {
-          @Override
-          public Void doInBackground(Void... params) {
+    asyncTaskExecutor.submit(Tasks.MARK_CALL_READ, () -> {
+      StringBuilder where = new StringBuilder();
+      where.append(CallLog.Calls.TYPE).append(" = ").append(CallLog.Calls.MISSED_TYPE);
+      where.append(" AND ");
 
-            StringBuilder where = new StringBuilder();
-            where.append(CallLog.Calls.TYPE).append(" = ").append(CallLog.Calls.MISSED_TYPE);
-            where.append(" AND ");
+      Long[] callIdLongs = new Long[callIds.length];
+      for (int i = 0; i < callIds.length; i++) {
+        callIdLongs[i] = callIds[i];
+      }
+      where
+          .append(CallLog.Calls._ID)
+          .append(" IN (" + TextUtils.join(",", callIdLongs) + ")");
 
-            Long[] callIdLongs = new Long[callIds.length];
-            for (int i = 0; i < callIds.length; i++) {
-              callIdLongs[i] = callIds[i];
-            }
-            where
-                .append(CallLog.Calls._ID)
-                .append(" IN (" + TextUtils.join(",", callIdLongs) + ")");
-
-            ContentValues values = new ContentValues(1);
-            values.put(CallLog.Calls.IS_READ, "1");
-            context
-                .getContentResolver()
-                .update(CallLog.Calls.CONTENT_URI, values, where.toString(), null);
-            return null;
-          }
-        });
+      ContentValues values = new ContentValues(1);
+      values.put(CallLog.Calls.IS_READ, "1");
+      context
+          .getContentResolver()
+          .update(CallLog.Calls.CONTENT_URI, values, where.toString(), null);
+    });
   }
 
-  /** The enumeration of {@link AsyncTask} objects used in this class. */
+  /** The enumeration of objects used in this class. */
   public enum Tasks {
     DELETE_VOICEMAIL,
     MARK_VOICEMAIL_READ,
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 544f1bc..6858df2 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -30,7 +30,6 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.provider.BlockedNumberContract;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
@@ -229,7 +228,7 @@
   public CharSequence dayGroupHeaderText;
   public boolean isAttachedToWindow;
 
-  public AsyncTask<Void, Void, ?> asyncTask;
+  public CallLogAdapter.LoadDataTaskInterface asyncTask;
   private CallDetailsEntries callDetailsEntries;
 
   private CallLogListItemViewHolder(
@@ -1095,7 +1094,7 @@
         ContactSource.Type contactSourceType);
   }
 
-  private static class DeleteCallTask extends AsyncTask<Void, Void, Void> {
+  private static class DeleteCallTask implements Runnable {
     // Using a weak reference to hold the Context so that there is no memory leak.
     private final WeakReference<Context> contextWeakReference;
 
@@ -1106,15 +1105,15 @@
       this.callIdsStr = concatCallIds(callIdsArray);
     }
 
-    @Override
     // Suppress the lint check here as the user will not be able to see call log entries if
     // permission.WRITE_CALL_LOG is not granted.
     @SuppressLint("MissingPermission")
     @RequiresPermission(value = permission.WRITE_CALL_LOG)
-    protected Void doInBackground(Void... params) {
+    @Override
+    public void run() {
       Context context = contextWeakReference.get();
       if (context == null) {
-        return null;
+        return;
       }
 
       if (callIdsStr != null) {
@@ -1128,13 +1127,8 @@
             .getContentResolver()
             .notifyChange(Calls.CONTENT_URI, null);
       }
-
-      return null;
     }
 
-    @Override
-    public void onPostExecute(Void result) {}
-
     private String concatCallIds(long[] callIds) {
       if (callIds == null || callIds.length == 0) {
         return null;
diff --git a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java
index cd67296..7a479a6 100644
--- a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java
+++ b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java
@@ -26,9 +26,9 @@
 import android.media.AudioManager;
 import android.media.MediaPlayer;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.provider.CallLog;
 import android.provider.VoicemailContract;
@@ -68,6 +68,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
@@ -392,19 +393,13 @@
 
   /** Checks to see if we have content available for this voicemail. */
   protected void checkForContent(final OnContentCheckedListener callback) {
-    asyncTaskExecutor.submit(
-        Tasks.CHECK_FOR_CONTENT,
-        new AsyncTask<Void, Void, Boolean>() {
-          @Override
-          public Boolean doInBackground(Void... params) {
-            return queryHasContent(voicemailUri);
-          }
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    Handler handler = new Handler(Looper.getMainLooper());
 
-          @Override
-          public void onPostExecute(Boolean hasContent) {
-            callback.onContentChecked(hasContent);
-          }
-        });
+    executor.execute(() -> {
+      final boolean hasContent = queryHasContent(voicemailUri);
+      handler.post(() -> callback.onContentChecked(hasContent));
+    });
   }
 
   private boolean queryHasContent(Uri voicemailUri) {
@@ -459,37 +454,28 @@
         break;
     }
 
-    asyncTaskExecutor.submit(
-        Tasks.SEND_FETCH_REQUEST,
-        new AsyncTask<Void, Void, Void>() {
-
-          @Override
-          protected Void doInBackground(Void... voids) {
-            try (Cursor cursor =
-                context
-                    .getContentResolver()
-                    .query(
-                        voicemailUri, new String[] {Voicemails.SOURCE_PACKAGE}, null, null, null)) {
-              String sourcePackage;
-              if (!hasContent(cursor)) {
-                LogUtil.e(
-                    "VoicemailPlaybackPresenter.requestContent",
-                    "mVoicemailUri does not return a SOURCE_PACKAGE");
-                sourcePackage = null;
-              } else {
-                sourcePackage = cursor.getString(0);
-              }
-              // Send voicemail fetch request.
-              Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, voicemailUri);
-              intent.setPackage(sourcePackage);
-              LogUtil.i(
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+    executor.execute(() -> {
+      try (Cursor cursor = context.getContentResolver().query(
+              voicemailUri, new String[] {Voicemails.SOURCE_PACKAGE}, null, null, null)) {
+        String sourcePackage;
+        if (!hasContent(cursor)) {
+          LogUtil.e(
                   "VoicemailPlaybackPresenter.requestContent",
-                  "Sending ACTION_FETCH_VOICEMAIL to " + sourcePackage);
-              context.sendBroadcast(intent);
-            }
-            return null;
-          }
-        });
+                  "mVoicemailUri does not return a SOURCE_PACKAGE");
+          sourcePackage = null;
+        } else {
+          sourcePackage = cursor.getString(0);
+        }
+        // Send voicemail fetch request.
+        Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, voicemailUri);
+        intent.setPackage(sourcePackage);
+        LogUtil.i(
+                "VoicemailPlaybackPresenter.requestContent",
+                "Sending ACTION_FETCH_VOICEMAIL to " + sourcePackage);
+        context.sendBroadcast(intent);
+      }
+    });
     return true;
   }
 
@@ -975,14 +961,6 @@
         null);
   }
 
-  /** The enumeration of {@link AsyncTask} objects we use in this class. */
-  public enum Tasks {
-    CHECK_FOR_CONTENT,
-    CHECK_CONTENT_AFTER_CHANGE,
-    SHARE_VOICEMAIL,
-    SEND_FETCH_REQUEST
-  }
-
   /** Contract describing the behaviour we need from the ui we are controlling. */
   public interface PlaybackView {
 
@@ -1066,24 +1044,19 @@
 
     @Override
     public void onChange(boolean selfChange) {
-      asyncTaskExecutor.submit(
-          Tasks.CHECK_CONTENT_AFTER_CHANGE,
-          new AsyncTask<Void, Void, Boolean>() {
+      ExecutorService executor = Executors.newSingleThreadExecutor();
+      Handler handler = new Handler(Looper.getMainLooper());
 
-            @Override
-            public Boolean doInBackground(Void... params) {
-              return queryHasContent(voicemailUri);
-            }
-
-            @Override
-            public void onPostExecute(Boolean hasContent) {
-              if (hasContent && context != null && isWaitingForResult.getAndSet(false)) {
-                context.getContentResolver().unregisterContentObserver(FetchResultHandler.this);
-                showShareVoicemailButton(true);
-                prepareContent();
-              }
-            }
-          });
+      executor.execute(() -> {
+        final boolean hasContent = queryHasContent(voicemailUri);
+        handler.post(() -> {
+          if (hasContent && context != null && isWaitingForResult.getAndSet(false)) {
+            context.getContentResolver().unregisterContentObserver(FetchResultHandler.this);
+            showShareVoicemailButton(true);
+            prepareContent();
+          }
+        });
+      });
     }
   }
 }
diff --git a/java/com/android/dialer/callstats/CallStatsDetailActivity.java b/java/com/android/dialer/callstats/CallStatsDetailActivity.java
index 93c4ccd..d30d3c8 100644
--- a/java/com/android/dialer/callstats/CallStatsDetailActivity.java
+++ b/java/com/android/dialer/callstats/CallStatsDetailActivity.java
@@ -20,8 +20,9 @@
 
 import android.content.Intent;
 import android.content.res.Resources;
-import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.CallLog.Calls;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.text.TextUtils;
@@ -50,6 +51,9 @@
 import com.android.dialer.util.CallUtil;
 import com.android.dialer.widget.LinearColorBar;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 /**
  * Activity to display detailed information about a callstat item
  */
@@ -89,18 +93,21 @@
   private CallStatsDetails mTotalData;
   private String mNumber = null;
 
-  private class UpdateContactTask extends AsyncTask<String, Void, ContactInfo> {
-    @Override
-    protected ContactInfo doInBackground(String... strings) {
-      return mContactInfoHelper.lookupNumber(strings[0], strings[1]);
-    }
+  private class UpdateContactTask {
 
-    @Override
-    protected void onPostExecute(ContactInfo info) {
-      if (info != null) {
-        mData.updateFromInfo(info);
-        updateData();
-      }
+    private void execute(String number, String countryIso) {
+      ExecutorService executor = Executors.newSingleThreadExecutor();
+      Handler handler = new Handler(Looper.getMainLooper());
+
+      executor.execute(() -> {
+        final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
+        handler.post(() -> {
+          if (info != null) {
+            mData.updateFromInfo(info);
+            updateData();
+          }
+        });
+      });
     }
   }
 
diff --git a/java/com/android/dialer/common/concurrent/AsyncTaskExecutor.java b/java/com/android/dialer/common/concurrent/AsyncTaskExecutor.java
index 6def128..32ff8b5 100644
--- a/java/com/android/dialer/common/concurrent/AsyncTaskExecutor.java
+++ b/java/com/android/dialer/common/concurrent/AsyncTaskExecutor.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2023 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,14 +17,12 @@
 
 package com.android.dialer.common.concurrent;
 
-import android.os.AsyncTask;
-
 import androidx.annotation.MainThread;
 
 import java.util.concurrent.Executor;
 
 /**
- * Interface used to submit {@link AsyncTask} objects to run in the background.
+ * Interface used to submit objects to run in the background.
  *
  * <p>This interface has a direct parallel with the {@link Executor} interface. It exists to
  * decouple the mechanics of AsyncTask submission from the description of how that AsyncTask will
@@ -39,15 +38,9 @@
  */
 public interface AsyncTaskExecutor {
 
-  /**
-   * Executes the given AsyncTask with the default Executor.
-   *
-   * <p>This method <b>must only be called from the ui thread</b>.
-   *
-   * <p>The identifier supplied is any Object that can be used to identify the task later. Most
-   * commonly this will be an enum which the tests can also refer to. {@code null} is also accepted,
-   * though of course this won't help in identifying the task later.
-   */
   @MainThread
-  <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params);
+  void submit(Object identifier, Runnable runnable);
+
+  @MainThread
+  void submit(Object identifier, Runnable runnable, Runnable postExecuteRunnable);
 }
diff --git a/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java b/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java
index 7c437df..d925b34 100644
--- a/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java
+++ b/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java
@@ -17,6 +17,8 @@
 package com.android.dialer.common.concurrent;
 
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
 
 import androidx.annotation.MainThread;
 
@@ -50,16 +52,25 @@
   static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor {
 
     private final Executor executor;
+    private final Handler handler;
 
     public SimpleAsyncTaskExecutor(Executor executor) {
       this.executor = executor;
+      this.handler = new Handler(Looper.getMainLooper());
     }
 
     @Override
     @MainThread
-    public <T> AsyncTask<T, ?, ?> submit(Object identifer, AsyncTask<T, ?, ?> task, T... params) {
+    public void submit(Object identifer, Runnable runnable) {
       Assert.isMainThread();
-      return task.executeOnExecutor(executor, params);
+      executor.execute(runnable);
+    }
+
+    @Override
+    public void submit(Object identifier, Runnable runnable, Runnable postExecuteRunnable) {
+      Assert.isMainThread();
+      executor.execute(runnable);
+      handler.post(postExecuteRunnable);
     }
   }
 }
diff --git a/java/com/android/dialer/helplines/LoadHelplinesTask.java b/java/com/android/dialer/helplines/LoadHelplinesTask.java
index 049cdba..291dd36 100644
--- a/java/com/android/dialer/helplines/LoadHelplinesTask.java
+++ b/java/com/android/dialer/helplines/LoadHelplinesTask.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2019-2021 The LineageOS Project
- * Copyright (C) 2023 The LineageOS Project
+ * Copyright (C) 2019-2023 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +16,8 @@
 package com.android.dialer.helplines;
 
 import android.content.res.Resources;
-import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 
@@ -27,10 +27,12 @@
 import org.lineageos.lib.phone.spn.Item;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
-public class LoadHelplinesTask extends AsyncTask<Void, Integer, List<HelplineItem>> {
+public class LoadHelplinesTask {
 
     @NonNull
     private final Resources mResources;
@@ -39,53 +41,64 @@
     @NonNull
     private final Callback mCallback;
 
+    private final ExecutorService mExecutor;
+    private final Handler mHandler;
+
     LoadHelplinesTask(@NonNull Resources resources, @NonNull SubscriptionManager subManager,
                       @NonNull Callback callback) {
         mResources = resources;
         mSubManager = subManager;
         mCallback = callback;
+
+        mExecutor = Executors.newSingleThreadExecutor();
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
-    @Override
-    protected List<HelplineItem> doInBackground(Void... voids) {
-        List<HelplineItem> helplineList = new ArrayList<>();
-        /* when the network's and the user's country iso differ from each other,
-         * include the iso code in the name so one can be sure that the number is the correct one
-         * (think of accidential roaming close to the country border) */
-        boolean addCountryCode = false;
+    public void execute() {
+        mExecutor.execute(() -> {
+            final List<HelplineItem> helplineList = new ArrayList<>();
+            /* when the network's and the user's country iso differ from each other,
+             * include the iso code in the name so one can be sure that the number is the correct
+             * one (think of accidental roaming close to the country border) */
+            boolean addCountryCode = false;
 
-        List<SubscriptionInfo> subList = getSubscriptionInfos();
-        if (subList != null) {
-            String localeCountryIso =
-                    mResources.getConfiguration().locale.getCountry().toLowerCase();
-            List<String> alreadyProcessedMccs = new ArrayList<>();
-            for (SubscriptionInfo subInfo : subList) {
-                String subCountryIso = subInfo.getCountryIso();
-                if (!subCountryIso.equals(localeCountryIso)) {
-                    addCountryCode = true;
-                }
+            List<SubscriptionInfo> subList = getSubscriptionInfos();
+            if (subList != null) {
+                String localeCountryIso = mResources.getConfiguration().getLocales().get(0)
+                        .getCountry().toLowerCase();
+                List<String> alreadyProcessedMccs = new ArrayList<>();
+                for (SubscriptionInfo subInfo : subList) {
+                    String subCountryIso = subInfo.getCountryIso();
+                    if (!subCountryIso.equals(localeCountryIso)) {
+                        addCountryCode = true;
+                    }
 
-                String mcc = String.valueOf(subInfo.getMcc());
-                if (alreadyProcessedMccs.contains(mcc)) {
-                    continue;
-                }
-                alreadyProcessedMccs.add(mcc);
+                    String mcc = subInfo.getMccString();
+                    if (alreadyProcessedMccs.contains(mcc)) {
+                        continue;
+                    }
+                    alreadyProcessedMccs.add(mcc);
 
-                SensitivePhoneNumbers spn = SensitivePhoneNumbers.getInstance();
-                ArrayList<Item> pns = spn.getSensitivePnInfosForMcc(mcc);
-                int numPns = pns.size();
-                for (int i = 0; i < numPns; i++) {
-                    Item item = pns.get(i);
-                    helplineList.add(new HelplineItem(mResources, item,
-                            addCountryCode ? subCountryIso : ""));
-                    publishProgress(Math.round(i * 100 / numPns / subList.size()));
+                    SensitivePhoneNumbers spn = SensitivePhoneNumbers.getInstance();
+                    ArrayList<Item> pns = spn.getSensitivePnInfosForMcc(mcc);
+                    int numPns = pns.size();
+                    for (int i = 0; i < numPns; i++) {
+                        Item item = pns.get(i);
+                        helplineList.add(new HelplineItem(mResources, item,
+                                addCountryCode ? subCountryIso : ""));
+                        final int currentItem = i;
+                        mHandler.post(() -> {
+                            int progress = Math.round(currentItem * 100f / numPns / subList.size());
+                            mCallback.onLoadListProgress(progress);
+                        });
+                    }
                 }
             }
-        }
 
-        Collections.sort(helplineList, (a, b) -> a.getName().compareTo(b.getName()));
+            helplineList.sort(Comparator.comparing(HelplineItem::getName));
 
-        return helplineList;
+            mHandler.post(() -> mCallback.onLoadCompleted(helplineList));
+        });
     }
 
     private List<SubscriptionInfo> getSubscriptionInfos() {
@@ -101,18 +114,6 @@
         return subList;
     }
 
-    @Override
-    protected void onProgressUpdate(Integer... values) {
-        if (values.length > 0) {
-            mCallback.onLoadListProgress(values[0]);
-        }
-    }
-
-    @Override
-    protected void onPostExecute(List<HelplineItem> list) {
-        mCallback.onLoadCompleted(list);
-    }
-
     interface Callback {
         void onLoadListProgress(int progress);
 
diff --git a/java/com/android/dialer/shortcuts/PeriodicJobService.java b/java/com/android/dialer/shortcuts/PeriodicJobService.java
index 038a820..7d65b2a 100644
--- a/java/com/android/dialer/shortcuts/PeriodicJobService.java
+++ b/java/com/android/dialer/shortcuts/PeriodicJobService.java
@@ -102,7 +102,7 @@
     LogUtil.enterBlock("PeriodicJobService.onStopJob");
 
     if (refreshShortcutsTask != null) {
-      refreshShortcutsTask.cancel(false /* mayInterruptIfRunning */);
+      refreshShortcutsTask.cancel();
     }
     return false;
   }
diff --git a/java/com/android/dialer/shortcuts/RefreshShortcutsTask.java b/java/com/android/dialer/shortcuts/RefreshShortcutsTask.java
index 360aea4..a06f0b3 100644
--- a/java/com/android/dialer/shortcuts/RefreshShortcutsTask.java
+++ b/java/com/android/dialer/shortcuts/RefreshShortcutsTask.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2023 The LineageOS Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,53 +19,57 @@
 
 import android.app.job.JobParameters;
 import android.app.job.JobService;
-import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
 
-import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
-import androidx.annotation.WorkerThread;
 
-import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 
-/** {@link AsyncTask} used by the periodic job service to refresh dynamic and pinned shortcuts. */
-final class RefreshShortcutsTask extends AsyncTask<JobParameters, Void, JobParameters> {
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+/** Class used by the periodic job service to refresh dynamic and pinned shortcuts. */
+final class RefreshShortcutsTask {
 
   private final JobService jobService;
+  private final ExecutorService executor = Executors.newSingleThreadExecutor();
+  private final Handler handler = new Handler(Looper.getMainLooper());
+
+  private boolean mIsCancelled = false;
 
   RefreshShortcutsTask(@NonNull JobService jobService) {
     this.jobService = jobService;
   }
 
-  /** @param params array with length 1, provided from PeriodicJobService */
-  @Override
-  @NonNull
-  @WorkerThread
-  protected JobParameters doInBackground(JobParameters... params) {
-    Assert.isWorkerThread();
-    LogUtil.enterBlock("RefreshShortcutsTask.doInBackground");
+  /** @param param provided from PeriodicJobService */
+  public void execute(JobParameters param) {
+    executor.execute(() -> {
+      LogUtil.enterBlock("RefreshShortcutsTask.doInBackground");
 
-    // Dynamic shortcuts are refreshed from the UI but icons can become stale, so update them
-    // periodically using the job service.
-    //
-    // The reason that icons can become is stale is that there is no last updated timestamp for
-    // pictures; there is only a last updated timestamp for the entire contact row, which changes
-    // frequently (for example, when they are called their "times_contacted" is incremented).
-    // Relying on such a spuriously updated timestamp would result in too frequent shortcut updates,
-    // so instead we just allow the icon to become stale in the case that the contact's photo is
-    // updated, and then rely on the job service to periodically force update it.
-    new DynamicShortcuts(jobService, new IconFactory(jobService)).updateIcons(); // Blocking
-    new PinnedShortcuts(jobService).refresh(); // Blocking
+      // Dynamic shortcuts are refreshed from the UI but icons can become stale, so update them
+      // periodically using the job service.
+      //
+      // The reason that icons can become is stale is that there is no last updated timestamp for
+      // pictures; there is only a last updated timestamp for the entire contact row, which changes
+      // frequently (for example, when they are called their "times_contacted" is incremented).
+      // Relying on such a spuriously updated timestamp would result in too frequent shortcut
+      // updates, so instead we just allow the icon to become stale in the case that the contact's
+      // photo is updated, and then rely on the job service to periodically force update it.
+      new DynamicShortcuts(jobService, new IconFactory(jobService)).updateIcons(); // Blocking
+      new PinnedShortcuts(jobService).refresh(); // Blocking
 
-    return params[0];
+      if (!mIsCancelled) {
+        handler.post(() -> {
+          LogUtil.enterBlock("RefreshShortcutsTask.onPostExecute");
+          jobService.jobFinished(param, false /* needsReschedule */);
+        });
+      }
+    });
   }
 
-  @Override
-  @MainThread
-  protected void onPostExecute(JobParameters params) {
-    Assert.isMainThread();
-    LogUtil.enterBlock("RefreshShortcutsTask.onPostExecute");
-
-    jobService.jobFinished(params, false /* needsReschedule */);
+  public void cancel() {
+    mIsCancelled = true;
   }
 }
diff --git a/java/com/android/dialer/shortcuts/ShortcutUsageReporter.java b/java/com/android/dialer/shortcuts/ShortcutUsageReporter.java
index 3cd719b..f3ecc73 100644
--- a/java/com/android/dialer/shortcuts/ShortcutUsageReporter.java
+++ b/java/com/android/dialer/shortcuts/ShortcutUsageReporter.java
@@ -23,7 +23,8 @@
 import android.content.pm.ShortcutManager;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.PhoneLookup;
 import android.text.TextUtils;
@@ -36,8 +37,9 @@
 
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.AsyncTaskExecutor;
-import com.android.dialer.common.concurrent.AsyncTaskExecutors;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * Reports outgoing calls as shortcut usage.
@@ -50,8 +52,6 @@
  */
 public class ShortcutUsageReporter {
 
-  private static final AsyncTaskExecutor EXECUTOR = AsyncTaskExecutors.createThreadPoolExecutor();
-
   /**
    * Called when an outgoing call is added to the call list in order to report outgoing calls as
    * shortcut usage. This should be called exactly once for each outgoing call.
@@ -71,36 +71,36 @@
       return;
     }
 
-    EXECUTOR.submit(Task.ID, new Task(context), phoneNumber);
+    new Task(context).execute(phoneNumber);
   }
 
-  private static final class Task extends AsyncTask<String, Void, Void> {
+  private static final class Task {
     private static final String ID = "ShortcutUsageReporter.Task";
 
     private final Context context;
 
+    private final ExecutorService executor = Executors.newSingleThreadExecutor();
+    private final Handler handler = new Handler(Looper.getMainLooper());
+
     public Task(Context context) {
       this.context = context;
     }
 
-    /** @param phoneNumbers array with exactly one non-empty phone number */
-    @Override
-    @WorkerThread
-    protected Void doInBackground(@NonNull String... phoneNumbers) {
-      Assert.isWorkerThread();
+    /** @param phoneNumber non-empty phone number */
+    public void execute(@NonNull String phoneNumber) {
+      executor.execute(() -> {
+        String lookupKey = queryForLookupKey(phoneNumber);
+        if (!TextUtils.isEmpty(lookupKey)) {
+          LogUtil.i("ShortcutUsageReporter.backgroundLogUsage", "%s", lookupKey);
+          ShortcutManager shortcutManager =
+                  (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
 
-      String lookupKey = queryForLookupKey(phoneNumbers[0]);
-      if (!TextUtils.isEmpty(lookupKey)) {
-        LogUtil.i("ShortcutUsageReporter.backgroundLogUsage", "%s", lookupKey);
-        ShortcutManager shortcutManager =
-            (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
-
-        // Note: There may not currently exist a shortcut with the provided key, but it is logged
-        // anyway, so that launcher applications at least have the information should the shortcut
-        // be created in the future.
-        shortcutManager.reportShortcutUsed(lookupKey);
-      }
-      return null;
+          // Note: There may not currently exist a shortcut with the provided key, but it is logged
+          // anyway, so that launcher applications at least have the information should the shortcut
+          // be created in the future.
+          shortcutManager.reportShortcutUsed(lookupKey);
+        }
+      });
     }
 
     @Nullable
diff --git a/java/com/android/dialer/smartdial/SmartDialCursorLoader.java b/java/com/android/dialer/smartdial/SmartDialCursorLoader.java
index 205362c..314bbd6 100644
--- a/java/com/android/dialer/smartdial/SmartDialCursorLoader.java
+++ b/java/com/android/dialer/smartdial/SmartDialCursorLoader.java
@@ -16,11 +16,13 @@
 
 package com.android.dialer.smartdial;
 
-import android.content.AsyncTaskLoader;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+
+import androidx.loader.content.AsyncTaskLoader;
+
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.database.Database;
 import com.android.dialer.database.DialerDatabaseHelper;