Migrate to WorkerThread and BackgroundThread

Remove a bunch of low-utilization threads from the Phone
process, instead moving standard priority work onto a new
worker thread, and background work such as metrics onto
the existing BackgroundThread.

Bug: 390244513
Flag: com.android.internal.telephony.flags.thread_shred
Test: atest FrameworksTelephonyTests
Test: manually verified CUJs on Panther
Change-Id: I4f5176921ee130a2f480575d636b2aaa9f278ff4
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index 4579ae2..4b4e02f 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -257,3 +257,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+#
+# OWNER=nharold TARGET=25Q4
+flag {
+    name: "thread_shred"
+    namespace: "telephony"
+    description: "Consolidate a bunch of unneeded worker threads to save resources"
+    bug:"390244513"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index 6326d6c..ffefce3 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -71,6 +71,7 @@
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -369,48 +370,72 @@
 
         if (mFeatureFlags.asyncInitCarrierPrivilegesTracker()) {
             final Object localLock = new Object();
-            HandlerThread initializerThread =
-                    new HandlerThread("CarrierPrivilegesTracker Initializer") {
-                        @Override
-                        protected void onLooperPrepared() {
-                            synchronized (localLock) {
-                                localLock.notifyAll();
-                            }
+            if (mFeatureFlags.threadShred()) {
+                mCurrentHandler = new Handler(WorkerThread.get().getLooper()) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        switch(msg.what) {
+                            case ACTION_INITIALIZE_TRACKER:
+                                handleInitializeTracker();
+                                if (!hasMessagesOrCallbacks()) {
+                                    mCurrentHandler = CarrierPrivilegesTracker.this;
+                                }
+                                break;
+                            default:
+                                Message m = CarrierPrivilegesTracker.this.obtainMessage();
+                                m.copyFrom(msg);
+                                m.sendToTarget();
+                                if (!hasMessagesOrCallbacks()) {
+                                    mCurrentHandler = CarrierPrivilegesTracker.this;
+                                }
+                                break;
                         }
-                    };
-            synchronized (localLock) {
-                initializerThread.start();
-                while (true) {
-                    try {
-                        localLock.wait();
-                        break;
-                    } catch (InterruptedException ie) {
+                    }
+                };
+            } else {
+                HandlerThread initializerThread =
+                        new HandlerThread("CarrierPrivilegesTracker Initializer") {
+                            @Override
+                            protected void onLooperPrepared() {
+                                synchronized (localLock) {
+                                    localLock.notifyAll();
+                                }
+                            }
+                        };
+                synchronized (localLock) {
+                    initializerThread.start();
+                    while (true) {
+                        try {
+                            localLock.wait();
+                            break;
+                        } catch (InterruptedException ie) {
+                        }
                     }
                 }
+                mCurrentHandler = new Handler(initializerThread.getLooper()) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        switch(msg.what) {
+                            case ACTION_INITIALIZE_TRACKER:
+                                handleInitializeTracker();
+                                if (!hasMessagesOrCallbacks()) {
+                                    mCurrentHandler = CarrierPrivilegesTracker.this;
+                                    initializerThread.quitSafely();
+                                }
+                                break;
+                            default:
+                                Message m = CarrierPrivilegesTracker.this.obtainMessage();
+                                m.copyFrom(msg);
+                                m.sendToTarget();
+                                if (!hasMessagesOrCallbacks()) {
+                                    mCurrentHandler = CarrierPrivilegesTracker.this;
+                                    initializerThread.quitSafely();
+                                }
+                                break;
+                        }
+                    }
+                };
             }
-            mCurrentHandler = new Handler(initializerThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch(msg.what) {
-                        case ACTION_INITIALIZE_TRACKER:
-                            handleInitializeTracker();
-                            if (!hasMessagesOrCallbacks()) {
-                                mCurrentHandler = CarrierPrivilegesTracker.this;
-                                initializerThread.quitSafely();
-                            }
-                            break;
-                        default:
-                            Message m = CarrierPrivilegesTracker.this.obtainMessage();
-                            m.copyFrom(msg);
-                            m.sendToTarget();
-                            if (!hasMessagesOrCallbacks()) {
-                                mCurrentHandler = CarrierPrivilegesTracker.this;
-                                initializerThread.quitSafely();
-                            }
-                            break;
-                    }
-                }
-            };
         } else {
             mCurrentHandler = this;
         }
diff --git a/src/java/com/android/internal/telephony/GbaManager.java b/src/java/com/android/internal/telephony/GbaManager.java
index 047d5d5..ec194db 100644
--- a/src/java/com/android/internal/telephony/GbaManager.java
+++ b/src/java/com/android/internal/telephony/GbaManager.java
@@ -39,7 +39,9 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.RcsStats;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.telephony.Rlog;
 
 import java.util.NoSuchElementException;
@@ -66,6 +68,8 @@
     public static final int REQUEST_TIMEOUT_MS = 5000;
     private final RcsStats mRcsStats;
 
+    private final FeatureFlags mFeatureFlags;
+
     private final String mLogTag;
     private final Context mContext;
     private final int mSubId;
@@ -202,7 +206,7 @@
 
     @VisibleForTesting
     public GbaManager(Context context, int subId, String servicePackageName, int releaseTime,
-            RcsStats rcsStats) {
+            RcsStats rcsStats, Looper looper, FeatureFlags featureFlags) {
         mContext = context;
         mSubId = subId;
         mLogTag = "GbaManager[" + subId + "]";
@@ -210,9 +214,15 @@
         mServicePackageName = servicePackageName;
         mReleaseTime = releaseTime;
 
-        HandlerThread headlerThread = new HandlerThread(mLogTag);
-        headlerThread.start();
-        mHandler = new GbaManagerHandler(headlerThread.getLooper());
+        mFeatureFlags = featureFlags;
+
+        if (mFeatureFlags.threadShred()) {
+            mHandler = new GbaManagerHandler(looper);
+        } else {
+            HandlerThread headlerThread = new HandlerThread(mLogTag);
+            headlerThread.start();
+            mHandler = new GbaManagerHandler(headlerThread.getLooper());
+        }
 
         if (mReleaseTime < 0) {
             mHandler.sendEmptyMessage(EVENT_BIND_SERVICE);
@@ -224,9 +234,15 @@
      * create a GbaManager instance for a sub
      */
     public static GbaManager make(Context context, int subId,
-            String servicePackageName, int releaseTime) {
-        GbaManager gm = new GbaManager(context, subId, servicePackageName, releaseTime,
-                RcsStats.getInstance());
+            String servicePackageName, int releaseTime, FeatureFlags featureFlags) {
+        GbaManager gm;
+        if (featureFlags.threadShred()) {
+            gm = new GbaManager(context, subId, servicePackageName, releaseTime,
+                    RcsStats.getInstance(), WorkerThread.get().getLooper(), featureFlags);
+        } else {
+            gm = new GbaManager(context, subId, servicePackageName, releaseTime,
+                    RcsStats.getInstance(), null, featureFlags);
+        }
         synchronized (sGbaManagers) {
             sGbaManagers.put(subId, gm);
         }
@@ -521,11 +537,15 @@
     @VisibleForTesting
     public void destroy() {
         mHandler.removeCallbacksAndMessages(null);
-        mHandler.getLooper().quit();
+        if (!mFeatureFlags.threadShred()) {
+            mHandler.getLooper().quit();
+        }
         mRequestQueue.clear();
         mCallbacks.clear();
         unbindService();
-        sGbaManagers.remove(mSubId);
+        synchronized (sGbaManagers) {
+            sGbaManagers.remove(mSubId);
+        }
     }
 
     private void logv(String msg) {
diff --git a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
index 4d9196e..e681280 100644
--- a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
+++ b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -55,10 +57,19 @@
             final CommandsInterface commandsInterface) {
         synchronized (RadioInterfaceCapabilityController.class) {
             if (sInstance == null) {
-                final HandlerThread handlerThread = new HandlerThread("RHC");
-                handlerThread.start();
-                sInstance = new RadioInterfaceCapabilityController(radioConfig, commandsInterface,
-                        handlerThread.getLooper());
+                if (Flags.threadShred()) {
+                    sInstance = new RadioInterfaceCapabilityController(
+                            radioConfig,
+                            commandsInterface,
+                            WorkerThread.get().getLooper());
+                } else {
+                    final HandlerThread handlerThread = new HandlerThread("RHC");
+                    handlerThread.start();
+                    sInstance = new RadioInterfaceCapabilityController(
+                            radioConfig,
+                            commandsInterface,
+                            handlerThread.getLooper());
+                }
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
diff --git a/src/java/com/android/internal/telephony/TelephonyCountryDetector.java b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
index 1e07bc3..d5b7fad 100644
--- a/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
+++ b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
@@ -43,6 +43,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.util.WorkerThread;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -205,12 +206,19 @@
     public static synchronized TelephonyCountryDetector getInstance(@NonNull Context context,
             FeatureFlags featureFlags) {
         if (sInstance == null) {
-            HandlerThread handlerThread = new HandlerThread("TelephonyCountryDetector");
-            handlerThread.start();
-            sInstance = new TelephonyCountryDetector(handlerThread.getLooper(), context,
-                    context.getSystemService(LocationManager.class),
-                    context.getSystemService(ConnectivityManager.class),
-                    featureFlags);
+            if (featureFlags.threadShred()) {
+                sInstance = new TelephonyCountryDetector(WorkerThread.get().getLooper(), context,
+                        context.getSystemService(LocationManager.class),
+                        context.getSystemService(ConnectivityManager.class),
+                        featureFlags);
+            } else {
+                HandlerThread handlerThread = new HandlerThread("TelephonyCountryDetector");
+                handlerThread.start();
+                sInstance = new TelephonyCountryDetector(handlerThread.getLooper(), context,
+                        context.getSystemService(LocationManager.class),
+                        context.getSystemService(ConnectivityManager.class),
+                        featureFlags);
+            }
         }
         return sInstance;
     }
diff --git a/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java b/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
index e74e40e..063ee45 100644
--- a/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
+++ b/src/java/com/android/internal/telephony/analytics/TelephonyAnalytics.java
@@ -47,10 +47,12 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -116,9 +118,14 @@
         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
         mSlotIndex = mPhone.getPhoneId();
 
-        mHandlerThread = new HandlerThread(TelephonyAnalytics.class.getSimpleName());
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        if (Flags.threadShred()) {
+            mHandlerThread = null; // TODO: maybe this doesn't need to be a member variable
+            mHandler = new Handler(BackgroundThread.get().getLooper());
+        } else {
+            mHandlerThread = new HandlerThread(TelephonyAnalytics.class.getSimpleName());
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
         mExecutorService = Executors.newSingleThreadExecutor();
         mTelephonyAnalyticsUtil = TelephonyAnalyticsUtil.getInstance(mContext);
         initializeAnalyticsClasses();
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index 5fdb8ce..569a9c3 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -62,6 +62,7 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.internal.telephony.util.WorkerThread;
 
 import java.io.ByteArrayOutputStream;
 import java.util.List;
@@ -228,9 +229,11 @@
      */
     public static CatService getInstance(CommandsInterface ci,
             Context context, UiccProfile uiccProfile, int slotId) {
-        if (sCatServiceThread == null) {
-            sCatServiceThread = new HandlerThread("CatServiceThread");
-            sCatServiceThread.start();
+        if (!sFlags.threadShred()) {
+            if (sCatServiceThread == null) {
+                sCatServiceThread = new HandlerThread("CatServiceThread");
+                sCatServiceThread.start();
+            }
         }
         UiccCardApplication ca = null;
         IccFileHandler fh = null;
@@ -259,8 +262,13 @@
                         || uiccProfile == null) {
                     return null;
                 }
-                sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId,
-                        sCatServiceThread.getLooper());
+                if (sFlags.threadShred()) {
+                    sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId,
+                            WorkerThread.get().getLooper());
+                } else {
+                    sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId,
+                            sCatServiceThread.getLooper());
+                }
             } else if ((ir != null) && (mIccRecords != ir)) {
                 if (mIccRecords != null) {
                     mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]);
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
index e4ae592..a8fead4 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
@@ -44,7 +44,9 @@
 import com.android.internal.telephony.IDomainSelectionServiceController;
 import com.android.internal.telephony.ITransportSelectorCallback;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.telephony.util.WorkerThread;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -80,10 +82,9 @@
         long getMaximumDelay();
     }
 
-    private final HandlerThread mHandlerThread =
-            new HandlerThread("DomainSelectionControllerHandler");
-
+    private HandlerThread mHandlerThread; // effectively final
     private final Handler mHandler;
+
     // Only added or removed, never accessed on purpose.
     private final LocalLog mLocalLog = new LocalLog(30);
 
@@ -255,9 +256,15 @@
             @Nullable Looper looper, @Nullable BindRetry bindRetry) {
         mContext = context;
 
+        mHandlerThread = null;
         if (looper == null) {
-            mHandlerThread.start();
-            looper = mHandlerThread.getLooper();
+            if (Flags.threadShred()) {
+                looper = WorkerThread.get().getLooper();
+            } else {
+                mHandlerThread = new HandlerThread("DomainSelectionControllerHandler");
+                mHandlerThread.start();
+                looper = mHandlerThread.getLooper();
+            }
         }
         mHandler = new DomainSelectionControllerHandler(looper);
 
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index b95911f..73ea190 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -63,6 +63,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.PhoneConfigurationManager;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
@@ -138,7 +139,8 @@
 
     // Delay between dynamic ImsService queries.
     private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
-    private static final HandlerThread sHandlerThread = new HandlerThread(TAG);
+
+    private static HandlerThread sHandlerThread;
 
     private static ImsResolver sInstance;
 
@@ -149,9 +151,15 @@
             String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo,
             FeatureFlags featureFlags) {
         if (sInstance == null) {
-            sHandlerThread.start();
-            sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName,
-                    numSlots, repo, sHandlerThread.getLooper(), featureFlags);
+            if (featureFlags.threadShred()) {
+                sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName,
+                        numSlots, repo, WorkerThread.get().getLooper(), featureFlags);
+            } else {
+                sHandlerThread = new HandlerThread(TAG);
+                sHandlerThread.start();
+                sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName,
+                        numSlots, repo, sHandlerThread.getLooper(), featureFlags);
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 37c10eb..c61c7eb 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -28,6 +28,7 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.permission.LegacyPermissionManager;
@@ -51,6 +52,7 @@
 import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.telephony.util.WorkerThread;
 
 import java.io.PrintWriter;
 import java.util.HashSet;
@@ -264,7 +266,7 @@
     // Enable ImsServiceControllerTest and SipDelegateManagerTest cases if this is re-enabled.
     private static final boolean ENFORCE_SINGLE_SERVICE_FOR_SIP_TRANSPORT = false;
     private final ComponentName mComponentName;
-    private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
+    private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final LegacyPermissionManager mPermissionManager;
     private final FeatureFlags mFeatureFlags;
@@ -362,8 +364,17 @@
         mContext = context;
         mComponentName = componentName;
         mCallbacks = callbacks;
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        Looper looper;
+        if (featureFlags.threadShred()) {
+            mHandlerThread = null;
+            mHandler = new Handler(WorkerThread.get().getLooper());
+            looper = WorkerThread.get().getLooper();
+        } else {
+            mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+            looper = mHandlerThread.getLooper();
+        }
         mBackoff = new ExponentialBackoff(
                 mRebindRetry.getStartDelay(),
                 mRebindRetry.getMaximumDelay(),
@@ -373,7 +384,7 @@
         mPermissionManager = (LegacyPermissionManager) mContext.getSystemService(
                 Context.LEGACY_PERMISSION_SERVICE);
         mRepo = repo;
-        mImsEnablementTracker = new ImsEnablementTracker(mHandlerThread.getLooper(), componentName);
+        mImsEnablementTracker = new ImsEnablementTracker(looper, componentName);
         mFeatureFlags = featureFlags;
         mPackageManager = mContext.getPackageManager();
         if (mPackageManager != null) {
@@ -404,6 +415,7 @@
         mRepo = repo;
         mFeatureFlags = featureFlags;
         mImsEnablementTracker = new ImsEnablementTracker(handler.getLooper(), componentName);
+        mHandlerThread = null;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
index 079ff03..696c9ce 100644
--- a/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
+++ b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
@@ -29,8 +29,10 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.internal.telephony.flags.Flags;
 
 import java.util.HashMap;
 import java.util.List;
@@ -76,10 +78,14 @@
             };
 
     private DataConnectionStateTracker() {
-        HandlerThread handlerThread =
-                new HandlerThread(DataConnectionStateTracker.class.getSimpleName());
-        handlerThread.start();
-        mExecutor = new HandlerExecutor(new Handler(handlerThread.getLooper()));
+        if (Flags.threadShred()) {
+            mExecutor = BackgroundThread.getExecutor();
+        } else {
+            HandlerThread handlerThread =
+                    new HandlerThread(DataConnectionStateTracker.class.getSimpleName());
+            handlerThread.start();
+            mExecutor = new HandlerExecutor(new Handler(handlerThread.getLooper()));
+        }
     }
 
     /** Getting or Creating DataConnectionStateTracker based on phoneId */
diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
index cd5b7d6..e7352a4 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -47,6 +47,7 @@
 import android.telephony.data.DataCallResponse.LinkStatus;
 import android.text.TextUtils;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyStatsLog;
@@ -143,9 +144,14 @@
         mPhone = phone;
         mFeatureFlags = featureFlags;
 
-        HandlerThread handlerThread = new HandlerThread(mTag + "-thread");
-        handlerThread.start();
-        mHandler = new Handler(handlerThread.getLooper());
+        if (mFeatureFlags.threadShred()) {
+            mHandler = new Handler(BackgroundThread.get().getLooper());
+        } else {
+            HandlerThread handlerThread = new HandlerThread(mTag + "-thread");
+            handlerThread.start();
+            mHandler = new Handler(handlerThread.getLooper());
+        }
+
         mTelephonyManager = mPhone.getContext().getSystemService(TelephonyManager.class);
 
         dataNetworkController.registerDataNetworkControllerCallback(
diff --git a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
index 9ab52fb..dc0ee4e 100644
--- a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
+++ b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
@@ -28,21 +28,32 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
 
 /** Device state information like the fold state. */
 public class DeviceStateHelper {
     private int mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN;
 
     public DeviceStateHelper(Context context) {
-        HandlerThread mHandlerThread = new HandlerThread("DeviceStateHelperThread");
-        mHandlerThread.start();
-        context.getSystemService(DeviceStateManager.class)
-                .registerCallback(
-                        new HandlerExecutor(new Handler(mHandlerThread.getLooper())),
-                        state -> {
-                            updateFoldState(state.getIdentifier());
-                        });
+        if (Flags.threadShred()) {
+            context.getSystemService(DeviceStateManager.class)
+                    .registerCallback(
+                            BackgroundThread.getExecutor(),
+                            state -> {
+                                updateFoldState(state.getIdentifier());
+                            });
+        } else {
+            HandlerThread mHandlerThread = new HandlerThread("DeviceStateHelperThread");
+            mHandlerThread.start();
+            context.getSystemService(DeviceStateManager.class)
+                    .registerCallback(
+                            new HandlerExecutor(new Handler(mHandlerThread.getLooper())),
+                            state -> {
+                                updateFoldState(state.getIdentifier());
+                            });
+        }
     }
 
     private void updateFoldState(int posture) {
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 581d54c..8f9a96b 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -29,6 +29,8 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
@@ -263,9 +265,15 @@
         mAtoms = loadAtomsFromFile();
         mVoiceCallRatTracker = VoiceCallRatTracker.fromProto(mAtoms.voiceCallRatUsage);
 
-        mHandlerThread = new HandlerThread("PersistAtomsThread");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        if (Flags.threadShred()) {
+            mHandlerThread = null;
+            mHandler = new Handler(BackgroundThread.get().getLooper());
+        } else {
+            // TODO: we might be able to make mHandlerThread a local variable
+            mHandlerThread = new HandlerThread("PersistAtomsThread");
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
         mSaveImmediately = false;
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/VonrHelper.java b/src/java/com/android/internal/telephony/metrics/VonrHelper.java
index 8f14a86..24c0945 100644
--- a/src/java/com/android/internal/telephony/metrics/VonrHelper.java
+++ b/src/java/com/android/internal/telephony/metrics/VonrHelper.java
@@ -24,6 +24,7 @@
 import android.os.HandlerThread;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.flags.FeatureFlags;
 
@@ -39,14 +40,19 @@
     private final @NonNull FeatureFlags mFlags;
 
     private Handler mHandler;
+    private Handler mHandlerThread;
     private Map<Integer, Boolean> mPhoneVonrState = new ConcurrentHashMap<>();
 
     public VonrHelper(@NonNull FeatureFlags featureFlags) {
         this.mFlags = featureFlags;
         if (mFlags.vonrEnabledMetric()) {
-            HandlerThread mHandlerThread = new HandlerThread("VonrHelperThread");
-            mHandlerThread.start();
-            mHandler = new Handler(mHandlerThread.getLooper());
+            if (mFlags.threadShred()) {
+                mHandler = new Handler(BackgroundThread.get().getLooper());
+            } else {
+                HandlerThread mHandlerThread = new HandlerThread("VonrHelperThread");
+                mHandlerThread.start();
+                mHandler = new Handler(mHandlerThread.getLooper());
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 0f33463..d5fc541 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -178,6 +178,7 @@
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.internal.util.FunctionalUtils;
 
 import java.util.ArrayList;
@@ -834,9 +835,15 @@
      */
     public static void make(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
         if (sInstance == null) {
-            HandlerThread satelliteThread = new HandlerThread(TAG);
-            satelliteThread.start();
-            sInstance = new SatelliteController(context, satelliteThread.getLooper(), featureFlags);
+            if (featureFlags.threadShred()) {
+                sInstance = new SatelliteController(
+                        context, WorkerThread.get().getLooper(), featureFlags);
+            } else {
+                HandlerThread satelliteThread = new HandlerThread(TAG);
+                satelliteThread.start();
+                sInstance = new SatelliteController(
+                        context, satelliteThread.getLooper(), featureFlags);
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index 0c993cf..83c11ef 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -116,6 +116,7 @@
 import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -491,10 +492,14 @@
         mUiccController = UiccController.getInstance();
         mHandler = new Handler(looper);
 
-        HandlerThread backgroundThread = new HandlerThread(LOG_TAG);
-        backgroundThread.start();
+        if (mFeatureFlags.threadShred()) {
+            mBackgroundHandler = new Handler(WorkerThread.get().getLooper());
+        } else {
+            HandlerThread backgroundThread = new HandlerThread(LOG_TAG);
+            backgroundThread.start();
 
-        mBackgroundHandler = new Handler(backgroundThread.getLooper());
+            mBackgroundHandler = new Handler(backgroundThread.getLooper());
+        }
 
         mDefaultVoiceSubId = new WatchedInt(Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
@@ -549,12 +554,22 @@
         mSimState = new int[mTelephonyManager.getSupportedModemCount()];
         Arrays.fill(mSimState, TelephonyManager.SIM_STATE_UNKNOWN);
 
-        // Create a separate thread for subscription database manager. The database will be updated
-        // from a different thread.
-        HandlerThread handlerThread = new HandlerThread(LOG_TAG);
-        handlerThread.start();
-        mSubscriptionDatabaseManager = new SubscriptionDatabaseManager(context,
-                handlerThread.getLooper(), mFeatureFlags,
+        Looper dbLooper = null;
+
+        if (mFeatureFlags.threadShred()) {
+            dbLooper = WorkerThread.get().getLooper();
+        } else {
+            // Create a separate thread for subscription database manager.
+            // The database will be updated from a different thread.
+            HandlerThread handlerThread = new HandlerThread(LOG_TAG);
+            handlerThread.start();
+            dbLooper = handlerThread.getLooper();
+        }
+
+        mSubscriptionDatabaseManager = new SubscriptionDatabaseManager(
+                context,
+                dbLooper,
+                mFeatureFlags,
                 new SubscriptionDatabaseManagerCallback(mHandler::post) {
                     /**
                      * Called when database has been loaded into the cache.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
index 8898a0f..e97b1c0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
@@ -42,6 +42,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.telephony.IBootstrapAuthenticationCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.gba.GbaAuthRequest;
@@ -52,6 +53,7 @@
 import android.testing.TestableLooper;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.RcsStats;
 
 import org.junit.After;
@@ -97,30 +99,44 @@
         mMockGbaServiceBinder = mock(IGbaService.class);
         mMockCallback = mock(IBootstrapAuthenticationCallback.class);
         mMockRcsStats = mock(RcsStats.class);
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
         when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any(UserHandle.class)))
                 .thenReturn(true);
         when(mMockGbaServiceBinder.asBinder()).thenReturn(mMockBinder);
-        mTestGbaManager = new GbaManager(mMockContext, TEST_SUB_ID, null, 0, mMockRcsStats);
-        mHandler = mTestGbaManager.getHandler();
-        try {
-            mLooper = new TestableLooper(mHandler.getLooper());
-        } catch (Exception e) {
-            fail("Unable to create looper from handler.");
+
+        if (mFeatureFlags.threadShred()) {
+            mTestGbaManager = new GbaManager(
+                    mMockContext, TEST_SUB_ID, null, 0, mMockRcsStats,
+                    TestableLooper.get(this).getLooper(), mFeatureFlags);
+            monitorTestableLooper(TestableLooper.get(this));
+        } else {
+            if (Looper.myLooper() == null) {
+                Looper.prepare();
+            }
+            mTestGbaManager = new GbaManager(
+                    mMockContext, TEST_SUB_ID, null, 0, mMockRcsStats, null, mFeatureFlags);
+            mHandler = mTestGbaManager.getHandler();
+            try {
+                mLooper = new TestableLooper(mHandler.getLooper());
+            } catch (Exception e) {
+                fail("Unable to create looper from handler.");
+            }
+            monitorTestableLooper(mLooper);
         }
-        monitorTestableLooper(mLooper);
     }
 
     @After
     public void tearDown() throws Exception {
         log("tearDown");
-        mTestGbaManager.destroy();
+        if (mFeatureFlags.threadShred()) {
+            if (mTestGbaManager != null) mTestGbaManager.destroy();
+        } else {
+            mTestGbaManager.destroy();
+        }
         super.tearDown();
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testFailOnRequest() throws Exception {
         GbaAuthRequest request = createDefaultRequest();
 
@@ -134,6 +150,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testBindServiceOnRequest() throws Exception {
         mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         GbaAuthRequest request = createDefaultRequest();
@@ -148,6 +165,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testFailAndRetryOnRequest() throws RemoteException {
         when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any(UserHandle.class)))
                 .thenReturn(false);
@@ -168,6 +186,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testBindServiceWhenPackageNameChanged() {
         mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_TIME_60S);
@@ -187,6 +206,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testBindServiceWhenReleaseTimeChanged() {
         mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_NEVER);
@@ -199,6 +219,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testDontBindServiceWhenPackageNameChanged() {
         mTestGbaManager.overrideServicePackage(TEST_SERVICE2_NAME.getPackageName(), 123);
 
@@ -210,6 +231,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testDontBindServiceWhenReleaseTimeChanged() {
         mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_TIME_60S);
@@ -222,6 +244,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_THREAD_SHRED)
     public void testMetricsGbaEvent() throws Exception {
         mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_NEVER);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 6517b15..0817b7a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -143,6 +143,7 @@
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
+import com.android.internal.telephony.util.WorkerThread;
 import com.android.server.pm.permission.LegacyPermissionManagerService;
 
 import org.mockito.Mockito;
@@ -583,6 +584,7 @@
         doReturn(true).when(mFeatureFlags).hsumBroadcast();
         doReturn(true).when(mFeatureFlags).hsumPackageManager();
 
+        WorkerThread.reset();
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
         // For testing do not allow Log.WTF as it can cause test process to crash
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
index adc0c75..a92ba8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -223,7 +223,10 @@
                 mFeatureFlags);
 
         monitorTestableLooper(new TestableLooper(getBackgroundHandler().getLooper()));
-        monitorTestableLooper(new TestableLooper(getSubscriptionDatabaseManager().getLooper()));
+
+        if (!mFeatureFlags.threadShred()) {
+            monitorTestableLooper(new TestableLooper(getSubscriptionDatabaseManager().getLooper()));
+        }
 
         doAnswer(invocation -> {
             ((Runnable) invocation.getArguments()[0]).run();