Moving various common executors to a single location

Change-Id: I44bca49b8adb6fa22c3b48d10f674e42c28d792c
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 0fa3d86..216972c 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep;
 
-import static com.android.systemui.shared.system.ActivityManagerWrapper
-        .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -25,7 +25,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
@@ -43,7 +42,6 @@
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
     private final RecentsModel mRecentsModel;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
@@ -51,7 +49,6 @@
     public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
-        mMainThreadExecutor = new MainThreadExecutor();
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
@@ -63,19 +60,19 @@
         }
 
         mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mMainThreadExecutor.execute(new RecentsActivityCommand<>());
+        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
     public void onOverviewShown(boolean triggeredFromAltTab) {
-        mMainThreadExecutor.execute(new ShowRecentsCommand());
+        MAIN_EXECUTOR.execute(new ShowRecentsCommand());
     }
 
     public void onOverviewHidden() {
-        mMainThreadExecutor.execute(new HideRecentsCommand());
+        MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
     public void onTip(int actionType, int viewType) {
-        mMainThreadExecutor.execute(() ->
+        MAIN_EXECUTOR.execute(() ->
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
     }
 
@@ -161,7 +158,7 @@
             // Otherwise, start overview.
             mListener = mHelper.createActivityInitListener(provider::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    provider, mContext, mMainThreadExecutor.getHandler(),
+                    provider, mContext, MAIN_EXECUTOR.getHandler(),
                     provider.getRecentsLaunchDuration());
         }
 
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 577b175..19dd82f 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -34,8 +34,6 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -139,9 +137,6 @@
         return sConnected;
     }
 
-    public static final LooperExecutor BACKGROUND_EXECUTOR =
-            new LooperExecutor(UiThreadHelper.getBackgroundLooper());
-
     private RecentsModel mRecentsModel;
     private OverviewComponentObserver mOverviewComponentObserver;
     private OverviewCommandHelper mOverviewCommandHelper;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 4ecc39c..65e69b6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -17,6 +17,8 @@
 
 import static android.content.pm.PackageManager.MATCH_INSTANT;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -30,8 +32,12 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
@@ -45,11 +51,6 @@
 import java.util.List;
 import java.util.Map;
 
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
 /**
  * Utility class which loads and caches predicted items like instant apps and shortcuts, before
  * they can be displayed on the UI
@@ -77,7 +78,7 @@
 
     public DynamicItemCache(Context context, Runnable onUpdateCallback) {
         mContext = context;
-        mWorker = new Handler(LauncherModel.getWorkerLooper(), this::handleWorkerMessage);
+        mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
         mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
         mInstantAppResolver = InstantAppResolver.newInstance(context);
         mOnUpdateCallback = onUpdateCallback;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index 7d8fab1..a12917f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.appprediction;
 
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionManager;
@@ -38,9 +41,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.util.UiThreadHelper;
-
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 
 /**
  * Subclass of app tracker which publishes the data to the prediction engine and gets back results.
@@ -65,7 +65,7 @@
 
     public PredictionAppTracker(Context context) {
         mContext = context;
-        mMessageHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleMessage);
+        mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage);
         InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
 
         mMessageHandler.sendEmptyMessage(MSG_INIT);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index d627a7f..3f4dfd2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -22,9 +22,9 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 
 import android.animation.Animator;
@@ -47,6 +47,8 @@
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.UiThread;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -54,7 +56,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
@@ -75,8 +76,6 @@
 
 import java.util.function.Consumer;
 
-import androidx.annotation.UiThread;
-
 /**
  * Base class for swipe up handler with some utility methods
  */
@@ -126,7 +125,7 @@
 
     protected Runnable mGestureEndCallback;
 
-    protected final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
+    protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
     protected MultiStateCallback mStateCallback;
 
     protected boolean mCanceled;
@@ -174,7 +173,7 @@
         if (effect == null) {
             return;
         }
-        BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect));
+        UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(effect));
     }
 
     public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index a94f25d..4249f1d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep;
 
-import static com.android.systemui.shared.system.ActivityManagerWrapper
-        .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,7 +28,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
@@ -47,7 +46,6 @@
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
     private final RecentsModel mRecentsModel;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
@@ -55,7 +53,6 @@
     public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
-        mMainThreadExecutor = new MainThreadExecutor();
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
@@ -67,19 +64,19 @@
         }
 
         mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mMainThreadExecutor.execute(new RecentsActivityCommand<>());
+        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
     public void onOverviewShown(boolean triggeredFromAltTab) {
-        mMainThreadExecutor.execute(new ShowRecentsCommand(triggeredFromAltTab));
+        MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
     }
 
     public void onOverviewHidden() {
-        mMainThreadExecutor.execute(new HideRecentsCommand());
+        MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
     public void onTip(int actionType, int viewType) {
-        mMainThreadExecutor.execute(() ->
+        MAIN_EXECUTOR.execute(() ->
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
     }
 
@@ -178,7 +175,7 @@
             // Otherwise, start overview.
             mListener = mHelper.createActivityInitListener(this::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(),
+                    this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
                     mAnimationProvider.getRecentsLaunchDuration());
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 4eb9df2..61b57e2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,9 +1,10 @@
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.os.Bundle;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.states.OverviewState;
@@ -52,7 +53,7 @@
 
             case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
                 try {
-                    final int leftMargin = new MainThreadExecutor().submit(() ->
+                    final int leftMargin = MAIN_EXECUTOR.submit(() ->
                             mLauncher.<RecentsView>getOverviewPanel().getLeftGestureMargin()).get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
                 } catch (ExecutionException e) {
@@ -65,7 +66,7 @@
 
             case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
                 try {
-                    final int rightMargin = new MainThreadExecutor().submit(() ->
+                    final int rightMargin = MAIN_EXECUTOR.submit(() ->
                             mLauncher.<RecentsView>getOverviewPanel().getRightGestureMargin()).
                             get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index c55f656..0acce02 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.util.Log;
 
@@ -25,6 +25,7 @@
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+
 import java.io.PrintWriter;
 
 /**
@@ -77,7 +78,7 @@
             mRecentsAnimationListener.removeListener(this);
             mRecentsAnimationListener.cancelListener();
             if (mLastAnimationRunning && mLastAnimationTarget != null) {
-                Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(),
+                Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                         finishAnimation
                                 ? mLastAnimationTarget::finishAnimation
                                 : mLastAnimationTarget::cancelAnimation);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 86ba855..440e8b9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,8 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -72,7 +74,6 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
@@ -82,8 +83,6 @@
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -106,8 +105,8 @@
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.RecentsAnimationListener;
 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
-
 import com.android.systemui.shared.system.TaskInfoCompat;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -138,10 +137,6 @@
 public class TouchInteractionService extends Service implements
         NavigationModeChangeListener, DisplayListener {
 
-    public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor();
-    public static final LooperExecutor BACKGROUND_EXECUTOR =
-            new LooperExecutor(UiThreadHelper.getBackgroundLooper());
-
     public static final EventLogArray TOUCH_INTERACTION_LOG =
             new EventLogArray("touch_interaction_log", 40);
 
@@ -161,9 +156,9 @@
         public void onInitialize(Bundle bundle) {
             mISystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
-            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
-            MAIN_THREAD_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
+            MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
             sIsInitialized = true;
         }
 
@@ -198,7 +193,7 @@
         @Override
         public void onAssistantVisibilityChanged(float visibility) {
             mLastAssistantVisibility = visibility;
-            MAIN_THREAD_EXECUTOR.execute(
+            MAIN_EXECUTOR.execute(
                     TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
@@ -214,13 +209,13 @@
                     isButton, gestureSwipeLeft, activityControl.getContainerType());
 
             if (completed && !isButton && shouldNotifyBackGesture()) {
-                BACKGROUND_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
+                UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
             }
         }
 
         public void onSystemUiStateChanged(int stateFlags) {
             mSystemUiStateFlags = stateFlags;
-            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
         }
 
         /** Deprecated methods **/
@@ -439,7 +434,7 @@
         if (mMode.hasGestures != newMode.hasGestures) {
             if (newMode.hasGestures) {
                 getSystemService(DisplayManager.class).registerDisplayListener(
-                        this, MAIN_THREAD_EXECUTOR.getHandler());
+                        this, MAIN_EXECUTOR.getHandler());
             } else {
                 getSystemService(DisplayManager.class).unregisterDisplayListener(this);
             }
@@ -897,7 +892,7 @@
     }
 
     public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
-        BACKGROUND_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+        UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
                 .startRecentsActivity(intent, null, listener, null, null));
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 14083dd..b1999d7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -15,11 +15,13 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.graphics.Rect;
 import android.util.ArraySet;
 
+import androidx.annotation.UiThread;
+
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
@@ -31,8 +33,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
-import androidx.annotation.UiThread;
-
 /**
  * Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners
  * on the main thread
@@ -82,7 +82,7 @@
         if (mCancelled) {
             targetSet.cancelAnimation();
         } else {
-            Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
                 for (SwipeAnimationListener listener : getListeners()) {
                     listener.onRecentsAnimationStart(targetSet);
                 }
@@ -92,14 +92,14 @@
 
     @Override
     public final void onAnimationCanceled(ThumbnailData thumbnailData) {
-        Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+        Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
             for (SwipeAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationCanceled();
             }
         });
         // TODO: handle the transition better instead of simply using a transition delay.
         if (thumbnailData != null) {
-            MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
+            MAIN_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
                     TRANSITION_DELAY);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index 381c27a..3619d3a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.graphics.Rect;
@@ -68,25 +68,25 @@
 
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
         mOnFinishListener.accept(this);
-        BACKGROUND_EXECUTOR.execute(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             controller.setInputConsumerEnabled(false);
             controller.finish(toRecents, sendUserLeaveHint);
 
             if (callback != null) {
-                MAIN_THREAD_EXECUTOR.execute(callback);
+                MAIN_EXECUTOR.execute(callback);
             }
         });
     }
 
     public void enableInputConsumer() {
-        BACKGROUND_EXECUTOR.submit(() -> {
+        UI_HELPER_EXECUTOR.submit(() -> {
             controller.hideCurrentInputMethod();
             controller.setInputConsumerEnabled(true);
         });
     }
 
     public void setWindowThresholdCrossed(boolean thresholdCrossed) {
-        BACKGROUND_EXECUTOR.execute(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
             if (mShouldMinimizeSplitScreen && thresholdCrossed) {
                 // NOTE: As a workaround for conflicting animations (Launcher animating the task
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 7fac813..b06d4bc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
 
 import static com.android.launcher3.Utilities.prefixTextWithIcon;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
@@ -41,7 +42,6 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -117,7 +117,7 @@
             return;
         }
 
-        Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
+        THREAD_POOL_EXECUTOR.execute(() -> {
             final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
                     task.getTopComponent().getPackageName(),
                     UserHandle.of(task.key.userId));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 3e1d61a..9464bd1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -35,9 +35,9 @@
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -228,7 +228,7 @@
                 return;
             }
 
-            BACKGROUND_EXECUTOR.execute(() -> {
+            UI_HELPER_EXECUTOR.execute(() -> {
                 TaskView taskView = getTaskView(taskId);
                 if (taskView == null) {
                     return;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
index 910fa0d..7beb9db 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
@@ -14,16 +14,17 @@
 
 package com.android.launcher3.uioverrides.plugins;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.os.Looper;
 
-import com.android.launcher3.LauncherModel;
 import com.android.systemui.shared.plugins.PluginInitializer;
 
 public class PluginInitializerImpl implements PluginInitializer {
     @Override
     public Looper getBgLooper() {
-        return LauncherModel.getWorkerLooper();
+        return MODEL_EXECUTOR.getLooper();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 78b48d7..858c3b6 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,20 +15,21 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
-import androidx.annotation.WorkerThread;
-
 /**
  * Sets alpha for the back button
  */
@@ -62,7 +63,7 @@
         // because of its high send frequency and data may be very different than the previous value
         // For example, send back alpha on uihandler to avoid flickering when setting its visibility
         mUiHandler = new Handler(this::handleUiMessage);
-        mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
+        mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage);
 
         onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
                 .addModeChangeListener(this::onNavigationModeChanged));
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 71ce32b..10f9feb 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep;
 
-import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
@@ -26,11 +26,12 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.util.LooperExecutor;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -43,7 +44,7 @@
 public class RecentTasksList extends TaskStackChangeListener {
 
     private final KeyguardManagerCompat mKeyguardManager;
-    private final MainThreadExecutor mMainThreadExecutor;
+    private final LooperExecutor mMainThreadExecutor;
     private final ActivityManagerWrapper mActivityManagerWrapper;
 
     // The list change id, increments as the task list changes in the system
@@ -55,8 +56,7 @@
 
     ArrayList<Task> mTasks = new ArrayList<>();
 
-
-    public RecentTasksList(MainThreadExecutor mainThreadExecutor,
+    public RecentTasksList(LooperExecutor mainThreadExecutor,
             KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
         mMainThreadExecutor = mainThreadExecutor;
         mKeyguardManager = keyguardManager;
@@ -70,7 +70,7 @@
      */
     public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
         // Kick off task loading in the background
-        BACKGROUND_EXECUTOR.execute(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
             mMainThreadExecutor.execute(() -> callback.accept(tasks));
         });
@@ -92,12 +92,12 @@
         if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
             // The list is up to date, send the callback on the next frame,
             // so that requestID can be returned first.
-            mMainThreadExecutor.getHandler().post(resultCallback);
+            mMainThreadExecutor.post(resultCallback);
             return requestLoadId;
         }
 
         // Kick off task loading in the background
-        BACKGROUND_EXECUTOR.execute(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
 
             mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index f9d2f11..4d1d9ef 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
@@ -22,7 +24,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 
@@ -92,14 +93,10 @@
     private static class Scheduler implements Runnable {
 
         private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
-        private MainThreadExecutor mMainThreadExecutor;
 
         public synchronized void schedule(RecentsActivityTracker tracker) {
             mPendingTracker = new WeakReference<>(tracker);
-            if (mMainThreadExecutor == null) {
-                mMainThreadExecutor = new MainThreadExecutor();
-            }
-            mMainThreadExecutor.execute(this);
+            MAIN_EXECUTOR.execute(this);
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d9ecdcd..2e59ed5 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,6 +15,10 @@
  */
 package com.android.quickstep;
 
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
 import android.annotation.TargetApi;
@@ -22,13 +26,12 @@
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.os.Build;
-import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -66,13 +69,12 @@
 
     private RecentsModel(Context context) {
         mContext = context;
-        HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        loaderThread.start();
-        mTaskList = new RecentTasksList(new MainThreadExecutor(),
+        Looper looper =
+                createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND);
+        mTaskList = new RecentTasksList(MAIN_EXECUTOR,
                 new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
-        mIconCache = new TaskIconCache(context, loaderThread.getLooper());
-        mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());
+        mIconCache = new TaskIconCache(context, looper);
+        mThumbnailCache = new TaskThumbnailCache(context, looper);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
         setupPackageListener();
     }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 5e01958..289a129 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,7 +28,6 @@
 import android.util.LruCache;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.cache.HandlerRunnable;
@@ -47,7 +47,6 @@
 public class TaskIconCache {
 
     private final Handler mBackgroundHandler;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final AccessibilityManager mAccessibilityManager;
 
     private final NormalizedIconLoader mIconLoader;
@@ -68,7 +67,6 @@
 
     public TaskIconCache(Context context, Looper backgroundLooper) {
         mBackgroundHandler = new Handler(backgroundLooper);
-        mMainThreadExecutor = new MainThreadExecutor();
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
 
         Resources res = context.getResources();
@@ -104,7 +102,7 @@
                     // We don't call back to the provided callback in this case
                     return;
                 }
-                mMainThreadExecutor.execute(() -> {
+                MAIN_EXECUTOR.execute(() -> {
                     task.icon = icon;
                     task.titleDescription = contentDescription;
                     callback.accept(task);
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 57c5a27..3b50c26 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,12 +15,14 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
-import com.android.launcher3.MainThreadExecutor;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.cache.HandlerRunnable;
@@ -30,13 +32,13 @@
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+
 import java.util.ArrayList;
 import java.util.function.Consumer;
 
 public class TaskThumbnailCache {
 
     private final Handler mBackgroundHandler;
-    private final MainThreadExecutor mMainThreadExecutor;
 
     private final int mCacheSize;
     private final ThumbnailCache mCache;
@@ -94,7 +96,6 @@
 
     public TaskThumbnailCache(Context context, Looper backgroundLooper) {
         mBackgroundHandler = new Handler(backgroundLooper);
-        mMainThreadExecutor = new MainThreadExecutor();
         mHighResLoadingState = new HighResLoadingState(context);
 
         Resources res = context.getResources();
@@ -168,7 +169,7 @@
                     // We don't call back to the provided callback in this case
                     return;
                 }
-                mMainThreadExecutor.execute(() -> {
+                MAIN_EXECUTOR.execute(() -> {
                     mCache.put(key, thumbnail);
                     callback.accept(thumbnail);
                     onEnd();
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 5fb27bc..34eb7f8 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -16,11 +16,20 @@
 
 package com.android.quickstep;
 
+import static junit.framework.TestCase.assertNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.ActivityManager;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.util.LooperExecutor;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.KeyguardManagerCompat;
@@ -31,14 +40,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import static junit.framework.TestCase.assertNull;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @SmallTest
 public class RecentTasksListTest {
 
@@ -49,7 +50,7 @@
 
     @Before
     public void setup() {
-        MainThreadExecutor mockMainThreadExecutor = mock(MainThreadExecutor.class);
+        LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
         KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class);
         mockActivityManagerWrapper = mock(ActivityManagerWrapper.class);
         mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat,
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index a6b53b9..351635c 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
@@ -28,9 +30,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.UserHandle;
@@ -39,6 +39,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.icons.BitmapInfo;
@@ -65,9 +67,6 @@
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
 
-    private static final int MSG_ADD_TO_QUEUE = 1;
-    private static final int MSG_FLUSH_QUEUE = 2;
-
     public static final int FLAG_ACTIVITY_PAUSED = 1;
     public static final int FLAG_LOADER_RUNNING = 2;
     public static final int FLAG_DRAG_AND_DROP = 4;
@@ -100,65 +99,56 @@
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
 
-    private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
+    @WorkerThread
+    private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
+        String encoded = info.encodeToString();
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+        strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
+        strings.add(encoded);
+        prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+    }
 
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ADD_TO_QUEUE: {
-                    Pair<Context, PendingInstallShortcutInfo> pair =
-                            (Pair<Context, PendingInstallShortcutInfo>) msg.obj;
-                    String encoded = pair.second.encodeToString();
-                    SharedPreferences prefs = Utilities.getPrefs(pair.first);
-                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-                    strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
-                    strings.add(encoded);
-                    prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
-                    return;
-                }
-                case MSG_FLUSH_QUEUE: {
-                    Context context = (Context) msg.obj;
-                    LauncherModel model = LauncherAppState.getInstance(context).getModel();
-                    if (model.getCallback() == null) {
-                        // Launcher not loaded
-                        return;
-                    }
-
-                    ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-                    SharedPreferences prefs = Utilities.getPrefs(context);
-                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-                    if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-                    if (strings == null) {
-                        return;
-                    }
-
-                    LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-                    for (String encoded : strings) {
-                        PendingInstallShortcutInfo info = decode(encoded, context);
-                        if (info == null) {
-                            continue;
-                        }
-
-                        String pkg = getIntentPackage(info.launchIntent);
-                        if (!TextUtils.isEmpty(pkg)
-                                && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
-                            if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                                    + info.launchIntent);
-                            continue;
-                        }
-
-                        // Generate a shortcut info to add into the model
-                        installQueue.add(info.getItemInfo());
-                    }
-                    prefs.edit().remove(APPS_PENDING_INSTALL).apply();
-                    if (!installQueue.isEmpty()) {
-                        model.addAndBindAddedWorkspaceItems(installQueue);
-                    }
-                    return;
-                }
-            }
+    @WorkerThread
+    private static void flushQueueInBackground(Context context) {
+        LauncherModel model = LauncherAppState.getInstance(context).getModel();
+        if (model.getCallback() == null) {
+            // Launcher not loaded
+            return;
         }
-    };
+
+        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+        if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
+        if (strings == null) {
+            return;
+        }
+
+        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        for (String encoded : strings) {
+            PendingInstallShortcutInfo info = decode(encoded, context);
+            if (info == null) {
+                continue;
+            }
+
+            String pkg = getIntentPackage(info.launchIntent);
+            if (!TextUtils.isEmpty(pkg)
+                    && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
+                if (DBG) {
+                    Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
+                }
+                continue;
+            }
+
+            // Generate a shortcut info to add into the model
+            installQueue.add(info.getItemInfo());
+        }
+        prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+        if (!installQueue.isEmpty()) {
+            model.addAndBindAddedWorkspaceItems(installQueue);
+        }
+    }
 
     public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
             UserHandle user) {
@@ -288,7 +278,7 @@
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
-        Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
+        MODEL_EXECUTOR.post(() -> addToQueue(context, info));
         flushInstallQueue(context);
     }
 
@@ -304,7 +294,7 @@
         if (sInstallQueueDisabledFlags != 0) {
             return;
         }
-        Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
+        MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
     }
 
     /**
@@ -484,7 +474,7 @@
                 appInfo.title = "";
                 appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
                 final WorkspaceItemInfo si = appInfo.makeWorkspaceItem();
-                if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
+                if (Looper.myLooper() == MODEL_EXECUTOR.getLooper()) {
                     app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
                 } else {
                     app.getModel().updateAndBindWorkspaceItem(() -> {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index bde87cb..8ee530f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.annotation.TargetApi;
@@ -42,6 +43,9 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.IntArray;
@@ -55,9 +59,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
 public class InvariantDeviceProfile {
 
     public static final String TAG = "IDP";
@@ -280,7 +281,7 @@
     public void setCurrentGrid(Context context, String gridName) {
         Context appContext = context.getApplicationContext();
         Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
-        new MainThreadExecutor().execute(() -> onConfigChanged(appContext));
+        MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
     }
 
     private void onConfigChanged(Context context) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index eed2377..da9617a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -18,20 +18,21 @@
 
 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
 import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
@@ -65,8 +66,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
-import androidx.annotation.Nullable;
-
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
  * LauncherModel object held in a static. Also provide APIs for updating the database state
@@ -78,21 +77,12 @@
 
     static final String TAG = "Launcher.Model";
 
-    private final MainThreadExecutor mUiExecutor = new MainThreadExecutor();
     @Thunk final LauncherAppState mApp;
     @Thunk final Object mLock = new Object();
     @Thunk
     LoaderTask mLoaderTask;
     @Thunk boolean mIsLoaderTaskRunning;
 
-    @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
-    private static final Looper mWorkerLooper;
-    static {
-        sWorkerThread.start();
-        mWorkerLooper = sWorkerThread.getLooper();
-    }
-    @Thunk static final Handler sWorker = new Handler(mWorkerLooper);
-
     // Indicates whether the current model data is valid or not.
     // We start off with everything not loaded. After that, we assume that
     // our monitoring of the package manager provides all updates and we never
@@ -318,7 +308,7 @@
             if (mCallbacks != null && mCallbacks.get() != null) {
                 final Callbacks oldCallbacks = mCallbacks.get();
                 // Clear any pending bind-runnables from the synchronized load process.
-                mUiExecutor.execute(oldCallbacks::clearPendingBinds);
+                MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds);
 
                 // If there is already one running, tell it to stop.
                 stopLoader();
@@ -362,7 +352,7 @@
 
             // Always post the loader task, instead of running directly (even on same thread) so
             // that we exit any nested synchronized blocks
-            sWorker.post(mLoaderTask);
+            MODEL_EXECUTOR.post(mLoaderTask);
         }
     }
 
@@ -429,8 +419,8 @@
      * use partial updates similar to {@link UserManagerCompat}
      */
     public void refreshShortcutsIfRequired() {
-        sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
-        sWorker.post(mShortcutPermissionCheckRunnable);
+        MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
+        MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
     }
 
     /**
@@ -457,14 +447,8 @@
     }
 
     public void enqueueModelUpdateTask(ModelUpdateTask task) {
-        task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor);
-
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            task.run();
-        } else {
-            // If we are not on the worker thread, then post to the worker handler
-            sWorker.post(task);
-        }
+        task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
+        MODEL_EXECUTOR.execute(task);
     }
 
     /**
@@ -540,14 +524,4 @@
         return mCallbacks != null ? mCallbacks.get() : null;
     }
 
-    /**
-     * @return the looper for the worker thread which can be used to start background tasks.
-     */
-    public static Looper getWorkerLooper() {
-        return mWorkerLooper;
-    }
-
-    public static void setWorkerPriority(final int priority) {
-        Process.setThreadPriority(sWorkerThread.getThreadId(), priority);
-    }
 }
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
deleted file mode 100644
index 5094682..0000000
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.launcher3;
-
-import android.os.Looper;
-
-import com.android.launcher3.util.LooperExecutor;
-
-/**
- * An executor service that executes its tasks on the main thread.
- *
- * Shutting down this executor is not supported.
- */
-public class MainThreadExecutor extends LooperExecutor {
-
-    public MainThreadExecutor() {
-        super(Looper.getMainLooper());
-    }
-}
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index b0da6b9..a1c77ef 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -30,13 +30,13 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
-import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Executors;
 
 import java.util.List;
 
@@ -103,7 +103,7 @@
             // grid.
             prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply();
         } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) {
-            new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+            new PrefInitTask(context).executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
         }
     }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 05336f2..6ddebe7 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -76,10 +76,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -112,8 +108,6 @@
     public static final boolean ATLEAST_OREO =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
 
-    public static final int SINGLE_FRAME_MS = 16;
-
     /**
      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
      */
@@ -136,18 +130,6 @@
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
 
-    // These values are same as that in {@link AsyncTask}.
-    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
-    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
-    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
-    private static final int KEEP_ALIVE = 1;
-    /**
-     * An {@link Executor} to be used with async task with no limit on the queue size.
-     */
-    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
-            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
-            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
-
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
 
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 6d1bc1a..003bcc1 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -1,5 +1,8 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -23,21 +26,23 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
-import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.LongSparseArray;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.icons.GraphicsUtils;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShadowGenerator;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SQLiteCacheHelper;
@@ -50,11 +55,8 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 
-import androidx.annotation.Nullable;
-
 public class WidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
@@ -68,23 +70,18 @@
      * Note: synchronized block used for this variable is expensive and the block should always
      * be posted to a background thread.
      */
-    @Thunk final Set<Bitmap> mUnusedBitmaps =
-            Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+    @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>());
 
     private final Context mContext;
     private final IconCache mIconCache;
     private final UserManagerCompat mUserManager;
     private final CacheDb mDb;
 
-    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-    @Thunk final Handler mWorkerHandler;
-
     public WidgetPreviewLoader(Context context, IconCache iconCache) {
         mContext = context;
         mIconCache = iconCache;
         mUserManager = UserManagerCompat.getInstance(context);
         mDb = new CacheDb(context);
-        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
     }
 
     /**
@@ -99,7 +96,7 @@
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
         PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
-        task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+        task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
 
         CancellationSignal signal = new CancellationSignal();
         signal.setOnCancelListener(task);
@@ -494,12 +491,7 @@
 
     private Drawable mutateOnMainThread(final Drawable drawable) {
         try {
-            return mMainThreadExecutor.submit(new Callable<Drawable>() {
-                @Override
-                public Drawable call() throws Exception {
-                    return drawable.mutate();
-                }
-            }).get();
+            return MAIN_EXECUTOR.submit(drawable::mutate).get();
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new RuntimeException(e);
@@ -607,7 +599,7 @@
 
             // Write the generated preview to the DB in the worker thread
             if (mVersions != null) {
-                mWorkerHandler.post(new Runnable() {
+                MODEL_EXECUTOR.post(new Runnable() {
                     @Override
                     public void run() {
                         if (!isCancelled()) {
@@ -637,7 +629,7 @@
             // recycled set immediately. Otherwise, it will be recycled after the preview is written
             // to disk.
             if (preview != null) {
-                mWorkerHandler.post(new Runnable() {
+                MODEL_EXECUTOR.post(new Runnable() {
                     @Override
                     public void run() {
                         synchronized (mUnusedBitmaps) {
@@ -658,7 +650,7 @@
             // in the tasks's onCancelled() call, and if cancelled while the task is writing to
             // disk, it will be cancelled in the task's onPostExecute() call.
             if (mBitmapToRecycle != null) {
-                mWorkerHandler.post(new Runnable() {
+                MODEL_EXECUTOR.post(new Runnable() {
                     @Override
                     public void run() {
                         synchronized (mUnusedBitmaps) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 812f444..d9eb311 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -90,6 +90,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
@@ -1159,7 +1160,7 @@
     }
 
     protected void setWallpaperDimension() {
-        Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+        Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() {
             @Override
             public void run() {
                 final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 6e7a1bd..5e13d00 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.compat;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
@@ -30,19 +32,17 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 @TargetApi(26)
 public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
 
@@ -120,7 +120,7 @@
                 }
             } else {
                 // Block the worker thread until the accept() is called.
-                new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() {
+                MODEL_EXECUTOR.execute(new Runnable() {
                     @Override
                     public void run() {
                         try {
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index 8a5eabc..052b0c3 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -16,22 +16,22 @@
 
 package com.android.launcher3.compat;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionInfo;
-import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -47,7 +47,6 @@
 
     @Thunk final PackageInstaller mInstaller;
     private final IconCache mCache;
-    private final Handler mWorker;
     private final Context mAppContext;
     private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>();
     private final LauncherAppsCompat mLauncherApps;
@@ -56,8 +55,7 @@
         mAppContext = context.getApplicationContext();
         mInstaller = context.getPackageManager().getPackageInstaller();
         mCache = LauncherAppState.getInstance(context).getIconCache();
-        mWorker = new Handler(LauncherModel.getWorkerLooper());
-        mInstaller.registerSessionCallback(mCallback, mWorker);
+        mInstaller.registerSessionCallback(mCallback, MODEL_EXECUTOR.getHandler());
         mLauncherApps = LauncherAppsCompat.getInstance(context);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index a2dcbf8..9fb1090 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
@@ -47,7 +48,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -55,7 +55,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.InstantAppResolver;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -234,7 +233,7 @@
                 mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
                 mWidgetCell.ensurePreview();
             }
-        }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper()));
+        }.executeOnExecutor(MODEL_EXECUTOR);
         // TODO: Create a worker looper executor and reuse that everywhere.
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 09c5e5b..f66d07e 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.dragndrop;
 
 import static com.android.launcher3.Utilities.getBadge;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -41,16 +42,19 @@
 import android.os.Looper;
 import android.view.View;
 
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
 import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.util.Themes;
@@ -58,10 +62,6 @@
 
 import java.util.Arrays;
 
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
 public class DragView extends View implements LauncherStateManager.StateListener {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -210,7 +210,7 @@
             return;
         }
         // Load the adaptive icon on a background thread and add the view in ui thread.
-        new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(new Runnable() {
+        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
                 Object[] outObj = new Object[1];
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index d8a1f99..0bb3fba 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -31,7 +33,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
@@ -85,7 +86,7 @@
         // Create the actual drawable on the UI thread to avoid race conditions with
         // FolderIcon draw pass
         try {
-            return new MainThreadExecutor().submit(() -> {
+            return MAIN_EXECUTOR.submit(() -> {
                 FolderIcon icon = launcher.findFolderIcon(folderId);
                 return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
             }).get();
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 9263a2a..747efe3 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BlurMaskFilter;
@@ -25,7 +27,6 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
@@ -35,7 +36,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 
@@ -157,7 +157,7 @@
         }
 
         mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
-        new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback);
+        UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback);
     }
 
     protected static Rect getDrawableBounds(Drawable d) {
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index efd39ee..71b4366 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.res.XmlResourceParser;
@@ -17,8 +19,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.R;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -180,10 +180,10 @@
             throw new FileNotFoundException(e.getMessage());
         }
 
-        LooperExecutor executor = new LooperExecutor(UiThreadHelper.getBackgroundLooper());
         try {
             return openPipeHelper(uri, MIME_TYPE_PNG, null,
-                    executor.submit(new LauncherPreviewRenderer(getContext(), idp)), BITMAP_WRITER);
+                    UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)),
+                    BITMAP_WRITER);
         } catch (Exception e) {
             throw new FileNotFoundException(e.getMessage());
         }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 55d58b9..5fb833c 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.icons;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -36,8 +39,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherFiles;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -59,8 +60,6 @@
 
     private static final String TAG = "Launcher.IconCache";
 
-    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
 
@@ -72,7 +71,7 @@
     private int mPendingIconRequestCount = 0;
 
     public IconCache(Context context, InvariantDeviceProfile inv) {
-        super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(),
+        super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
@@ -123,7 +122,7 @@
             final ItemInfoWithIcon info) {
         Preconditions.assertUIThread();
         if (mPendingIconRequestCount <= 0) {
-            LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND);
+            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
         }
         mPendingIconRequestCount ++;
 
@@ -135,7 +134,7 @@
                 } else if (info instanceof PackageItemInfo) {
                     getTitleAndIconForApp((PackageItemInfo) info, false);
                 }
-                mMainThreadExecutor.execute(() -> {
+                MAIN_EXECUTOR.execute(() -> {
                     caller.reapplyItemInfo(info);
                     onEnd();
                 });
@@ -148,7 +147,7 @@
     private void onIconRequestEnd() {
         mPendingIconRequestCount --;
         if (mPendingIconRequestCount <= 0) {
-            LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
     }
 
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index cc92076..47a0f53 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.logging;
 
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -92,9 +94,8 @@
     private static Handler getHandler() {
         synchronized (DATE_FORMAT) {
             if (sHandler == null) {
-                HandlerThread thread = new HandlerThread("file-logger");
-                thread.start();
-                sHandler = new Handler(thread.getLooper(), new LogWriterCallback());
+                sHandler = new Handler(createAndStartNewLooper("file-logger"),
+                        new LogWriterCallback());
             }
         }
         return sHandler;
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 018f93a..0a4f005 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.os.Looper;
 import android.util.Log;
 
@@ -25,11 +27,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
@@ -64,7 +65,7 @@
 
     public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
-        mUiExecutor = new MainThreadExecutor();
+        mUiExecutor = MAIN_EXECUTOR;
         mApp = app;
         mBgDataModel = dataModel;
         mBgAllAppsList = allAppsList;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 54249dc..8845ab3 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -32,7 +33,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ShortcutInfo;
-import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -772,7 +772,7 @@
                         new SdCardAvailableReceiver(mApp, pendingPackages),
                         new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                         null,
-                        new Handler(LauncherModel.getWorkerLooper()));
+                        MODEL_EXECUTOR.getHandler());
             }
         }
     }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index d7ab0dd..b7a19d3 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -31,17 +33,16 @@
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.LooperExecutor;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -61,7 +62,6 @@
     private final BgDataModel mBgDataModel;
     private final Handler mUiHandler;
 
-    private final Executor mWorkerExecutor;
     private final boolean mHasVerticalHotseat;
     private final boolean mVerifyChanges;
 
@@ -74,7 +74,6 @@
         mContext = context;
         mModel = model;
         mBgDataModel = dataModel;
-        mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
         mHasVerticalHotseat = hasVerticalHotseat;
         mVerifyChanges = verifyChanges;
         mUiHandler = new Handler(Looper.getMainLooper());
@@ -194,7 +193,7 @@
         item.spanX = spanX;
         item.spanY = spanY;
 
-        mWorkerExecutor.execute(new UpdateItemRunnable(item, () ->
+        ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () ->
                 new ContentWriter(mContext)
                         .put(Favorites.CONTAINER, item.container)
                         .put(Favorites.CELLX, item.cellX)
@@ -209,7 +208,7 @@
      * Update an item to the database in a specified container.
      */
     public void updateItemInDatabase(ItemInfo item) {
-        mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> {
+        ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> {
             ContentWriter writer = new ContentWriter(mContext);
             item.onAddToDatabase(writer);
             return writer;
@@ -229,7 +228,7 @@
 
         ModelVerifier verifier = new ModelVerifier();
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        mWorkerExecutor.execute(() -> {
+        ((Executor) MODEL_EXECUTOR).execute(() -> {
             // Write the item on background thread, as some properties might have been updated in
             // the background.
             final ContentWriter writer = new ContentWriter(mContext);
@@ -333,14 +332,14 @@
         if (mPreparingToUndo) {
             mDeleteRunnables.add(r);
         } else {
-            mWorkerExecutor.execute(r);
+            ((Executor) MODEL_EXECUTOR).execute(r);
         }
     }
 
     public void commitDelete() {
         mPreparingToUndo = false;
         for (Runnable runnable : mDeleteRunnables) {
-            mWorkerExecutor.execute(runnable);
+            ((Executor) MODEL_EXECUTOR).execute(runnable);
         }
         mDeleteRunnables.clear();
     }
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index e57a051..10378ee 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.notification;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.annotation.TargetApi;
@@ -31,7 +32,8 @@
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.launcher3.LauncherModel;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.SecureSettingsObserver;
@@ -43,8 +45,6 @@
 import java.util.List;
 import java.util.Map;
 
-import androidx.annotation.Nullable;
-
 /**
  * A {@link NotificationListenerService} that sends updates to its
  * {@link NotificationsChangedListener} when notifications are posted or canceled,
@@ -141,7 +141,7 @@
 
     public NotificationListener() {
         super();
-        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
+        mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback);
         mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
         sNotificationListenerInstance = this;
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index baaad65..1296a96 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -36,7 +37,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -51,9 +51,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
@@ -65,10 +63,8 @@
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
@@ -307,8 +303,7 @@
         setLayoutTransition(new LayoutTransition());
 
         // Load the shortcuts on a background thread and update the container as it animates.
-        final Looper workerLooper = LauncherModel.getWorkerLooper();
-        new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
+        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
                 mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
                 this, mShortcuts, notificationKeys));
     }
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index 446d4f8..a23cd6d 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.states;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
@@ -23,7 +25,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.MainThreadExecutor;
 
 import java.lang.ref.WeakReference;
 
@@ -94,16 +95,12 @@
     private static class Scheduler implements Runnable {
 
         private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null);
-        private MainThreadExecutor mMainThreadExecutor;
 
         public void schedule(InternalStateHandler handler) {
             synchronized (this) {
                 mPendingHandler = new WeakReference<>(handler);
-                if (mMainThreadExecutor == null) {
-                    mMainThreadExecutor = new MainThreadExecutor();
-                }
             }
-            mMainThreadExecutor.execute(this);
+            MAIN_EXECUTOR.execute(this);
         }
 
         @Override
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 4fd0f88..516c251 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.testing;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.os.Bundle;
 
@@ -23,7 +25,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -88,20 +89,20 @@
                 break;
 
             case TestProtocol.REQUEST_FREEZE_APP_LIST:
-                new MainThreadExecutor().execute(() ->
+                MAIN_EXECUTOR.execute(() ->
                         mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
                                 AllAppsStore.DEFER_UPDATES_TEST));
                 break;
 
             case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
-                new MainThreadExecutor().execute(() ->
+                MAIN_EXECUTOR.execute(() ->
                         mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
                                 AllAppsStore.DEFER_UPDATES_TEST));
                 break;
 
             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
                 try {
-                    final int deferUpdatesFlags = new MainThreadExecutor().submit(() ->
+                    final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
                             mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                             deferUpdatesFlags);
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 12d35e9..4ae84d8 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -16,6 +16,9 @@
  * limitations under the License.
  */
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -24,13 +27,10 @@
 import android.graphics.Point;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
 import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 
-import com.android.launcher3.MainThreadExecutor;
-
 import java.util.function.Consumer;
 
 /**
@@ -78,7 +78,7 @@
 
         // Listen for display manager change
         mContext.getSystemService(DisplayManager.class)
-                .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
+                .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
     }
 
     @Override
@@ -122,7 +122,7 @@
         if (mCallback != null) {
             Consumer<Context> callback = mCallback;
             mCallback = null;
-            new MainThreadExecutor().execute(() -> callback.accept(mContext));
+            MAIN_EXECUTOR.execute(() -> callback.accept(mContext));
         }
     }
 
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 7719f08..4080e21 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.Context;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager;
@@ -54,7 +56,7 @@
         mChangeHandler = new Handler(this::onChange);
 
         context.getSystemService(DisplayManager.class)
-                .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
+                .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
new file mode 100644
index 0000000..4d5ee49
--- /dev/null
+++ b/src/com/android/launcher3/util/Executors.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Various different executors used in Launcher
+ */
+public class Executors {
+
+    // These values are same as that in {@link AsyncTask}.
+    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+    private static final int KEEP_ALIVE = 1;
+
+    /**
+     * An {@link Executor} to be used with async task with no limit on the queue size.
+     */
+    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
+            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
+            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
+
+    /**
+     * Returns the executor for running tasks on the main thread.
+     */
+    public static final LooperExecutor MAIN_EXECUTOR =
+            new LooperExecutor(Looper.getMainLooper());
+
+    /**
+     * A background executor for using time sensitive actions where user is waiting for response.
+     */
+    public static final LooperExecutor UI_HELPER_EXECUTOR =
+            new LooperExecutor(createAndStartNewForegroundLooper("UiThreadHelper"));
+
+    /**
+     * Utility method to get a started handler thread statically
+     */
+    public static Looper createAndStartNewLooper(String name) {
+        return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT);
+    }
+
+    /**
+     * Utility method to get a started handler thread statically with the provided priority
+     */
+    public static Looper createAndStartNewLooper(String name, int priority) {
+        HandlerThread thread = new HandlerThread(name, priority);
+        thread.start();
+        return thread.getLooper();
+    }
+
+    /**
+     * Similar to {@link #createAndStartNewLooper(String)}, but starts the thread with
+     * foreground priority.
+     * Think before using
+     */
+    public static Looper createAndStartNewForegroundLooper(String name) {
+        return createAndStartNewLooper(name, Process.THREAD_PRIORITY_FOREGROUND);
+    }
+
+    /**
+     * Executor used for running Launcher model related tasks (eg loading icons or updated db)
+     */
+    public static final LooperExecutor MODEL_EXECUTOR =
+            new LooperExecutor(createAndStartNewLooper("launcher-loader"));
+}
diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java
index cc07469..8ac600f 100644
--- a/src/com/android/launcher3/util/LooperExecutor.java
+++ b/src/com/android/launcher3/util/LooperExecutor.java
@@ -16,7 +16,9 @@
 package com.android.launcher3.util;
 
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.Process;
 
 import java.util.List;
 import java.util.concurrent.AbstractExecutorService;
@@ -47,6 +49,13 @@
     }
 
     /**
+     * Same as execute, but never runs the action inline.
+     */
+    public void post(Runnable runnable) {
+        mHandler.post(runnable);
+    }
+
+    /**
      * Not supported and throws an exception when used.
      */
     @Override
@@ -79,7 +88,31 @@
      */
     @Override
     @Deprecated
-    public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+    public boolean awaitTermination(long l, TimeUnit timeUnit) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Returns the thread for this executor
+     */
+    public Thread getThread() {
+        return mHandler.getLooper().getThread();
+    }
+
+    /**
+     * Returns the looper for this executor
+     */
+    public Looper getLooper() {
+        return mHandler.getLooper();
+    }
+
+    /**
+     * Set the priority of a thread, based on Linux priorities.
+     * @param priority Linux priority level, from -20 for highest scheduling priority
+     *                to 19 for lowest scheduling priority.
+     * @see Process#setThreadPriority(int, int)
+     */
+    public void setThreadPriority(int priority) {
+        Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority);
+    }
 }
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index e185a31..fe9c2c4 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -15,12 +15,13 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.os.Looper;
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 
 import java.util.concurrent.ExecutionException;
@@ -43,7 +44,7 @@
                 mValue = mProvider.get(context.getApplicationContext());
             } else {
                 try {
-                    return new MainThreadExecutor().submit(() -> get(context)).get();
+                    return MAIN_EXECUTOR.submit(() -> get(context)).get();
                 } catch (InterruptedException|ExecutionException e) {
                     throw new RuntimeException(e);
                 }
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 7ab0d31..ed66422 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -16,9 +16,10 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.os.Looper;
 
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.config.FeatureFlags;
 
 /**
@@ -33,7 +34,7 @@
     }
 
     public static void assertWorkerThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
             throw new IllegalStateException();
         }
     }
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index cc442f9..f8d1632 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -15,14 +15,13 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.app.Activity;
 import android.content.Context;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.view.inputmethod.InputMethodManager;
 
 /**
@@ -30,25 +29,15 @@
  */
 public class UiThreadHelper {
 
-    private static HandlerThread sHandlerThread;
     private static Handler sHandler;
 
     private static final int MSG_HIDE_KEYBOARD = 1;
     private static final int MSG_SET_ORIENTATION = 2;
     private static final int MSG_RUN_COMMAND = 3;
 
-    public static Looper getBackgroundLooper() {
-        if (sHandlerThread == null) {
-            sHandlerThread =
-                    new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND);
-            sHandlerThread.start();
-        }
-        return sHandlerThread.getLooper();
-    }
-
     private static Handler getHandler(Context context) {
         if (sHandler == null) {
-            sHandler = new Handler(getBackgroundLooper(),
+            sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(),
                     new UiCallbacks(context.getApplicationContext()));
         }
         return sHandler;
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index acce308..61ba4e5 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,13 +16,14 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.os.Process;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -66,7 +67,7 @@
     @Override
     public void execute(Runnable command) {
         mTasks.add(command);
-        LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
     }
 
     @Override
@@ -108,7 +109,7 @@
         if (mLauncher != null) {
             mLauncher.clearPendingExecutor(this);
         }
-        LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_DEFAULT);
+        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
     }
 
     protected boolean isCompleted() {
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 5c24687..2ad80cf 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -198,7 +200,7 @@
         private float mOffsetX;
 
         public OffsetHandler(Context context) {
-            super(UiThreadHelper.getBackgroundLooper());
+            super(UI_HELPER_EXECUTOR.getLooper());
             mInterpolator = Interpolators.DEACCEL_1_5;
             mWM = WallpaperManager.getInstance(context);
         }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index e09a9e8..b146452 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -18,11 +18,11 @@
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
-import static com.android.launcher3.Utilities.isRtl;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -42,7 +42,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
-import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -51,11 +50,17 @@
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -67,13 +72,6 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
@@ -717,7 +715,7 @@
     @UiThread
     public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) {
         IconLoadResult result = new IconLoadResult();
-        new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
+        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
             RectF position = new RectF();
             getLocationBoundsForView(l, v, isOpening, position);
             getIconResult(l, v, info, position, result);
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index dce839f..f3fd7ca 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
 
@@ -96,7 +97,7 @@
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
 
         if (Utilities.ATLEAST_OREO) {
-            setExecutor(Utilities.THREAD_POOL_EXECUTOR);
+            setExecutor(Executors.THREAD_POOL_EXECUTOR);
         }
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
@@ -332,12 +333,7 @@
         if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
             mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
             if (mAutoAdvanceRunnable == null) {
-                mAutoAdvanceRunnable = new Runnable() {
-                    @Override
-                    public void run() {
-                        runAutoAdvance();
-                    }
-                };
+                mAutoAdvanceRunnable = this::runAutoAdvance;
             }
 
             handler.removeCallbacks(mAutoAdvanceRunnable);
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index fc19baa..6d636cf 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -19,12 +19,12 @@
 
 import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import static org.junit.Assert.assertTrue;
 
 import static java.lang.System.exit;
 
-import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -48,7 +48,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.model.AppLaunchTracker;
@@ -56,6 +55,7 @@
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
@@ -90,7 +90,7 @@
     public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
     private static final String TAG = "AbstractLauncherUiTest";
 
-    protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+    protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
     protected final LauncherInstrumentation mLauncher =
             new LauncherInstrumentation(getInstrumentation());
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
index b564a1a..d85dd3a 100644
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ b/tests/src/com/android/launcher3/util/Condition.java
@@ -1,8 +1,8 @@
 package com.android.launcher3.util;
 
-import androidx.test.uiautomator.UiObject2;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
-import com.android.launcher3.MainThreadExecutor;
+import androidx.test.uiautomator.UiObject2;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -16,7 +16,7 @@
      * Converts the condition to be run on UI thread.
      */
     static Condition runOnUiThread(final Condition condition) {
-        final MainThreadExecutor executor = new MainThreadExecutor();
+        final LooperExecutor executor = MAIN_EXECUTOR;
         return () -> {
             final AtomicBoolean value = new AtomicBoolean(false);
             final Throwable[] exceptions = new Throwable[1];
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index 0235f95..8f89173 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
 
@@ -23,7 +24,6 @@
 import static org.junit.Assert.fail;
 
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -72,9 +72,7 @@
     private static final Handler POSTPONED_EVENT_RESUME_HANDLER = createEventResumeHandler();
 
     private static Handler createEventResumeHandler() {
-        final HandlerThread thread = new HandlerThread("RaceConditionEventResumer");
-        thread.start();
-        return new Handler(thread.getLooper());
+        return new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
     }
 
     /**