Replaced TelephonyNetworkFactory with TelephonyNetworkProvider

Adapted the new network provider model. The new TelephonyNetworkProvider
is a singleton responsible for providing all telephony related networks
including networks on cellular and IWLAN across all active SIMs.

Flag: com.android.internal.telephony.flags.support_network_provider
Fix: 343370895
Test: Comprehensive telephony tests including mobile data and voice call
      tests.
Test: atest TelephonyNetworkProviderTest
Change-Id: I99b236131606b83c2733a313715c313486b9a953
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 80d6f61..bd9b21e 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -181,4 +181,14 @@
   bug:"353723350"
 }
 
+# OWNER=jackyu TARGET=25Q1
+flag {
+  name: "support_network_provider"
+  namespace: "telephony"
+  description: "Deprecate network factory and adapt the new network provider model from connectivity service"
+  bug: "343370895"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
 
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 2cd6021..f7ce388 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -46,6 +46,7 @@
 import com.android.internal.telephony.data.CellularNetworkValidator;
 import com.android.internal.telephony.data.PhoneSwitcher;
 import com.android.internal.telephony.data.TelephonyNetworkFactory;
+import com.android.internal.telephony.data.TelephonyNetworkProvider;
 import com.android.internal.telephony.euicc.EuiccCardController;
 import com.android.internal.telephony.euicc.EuiccController;
 import com.android.internal.telephony.flags.FeatureFlags;
@@ -101,6 +102,7 @@
     static private SimultaneousCallingTracker sSimultaneousCallingTracker;
     static private PhoneSwitcher sPhoneSwitcher;
     static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
+    private static TelephonyNetworkProvider sTelephonyNetworkProvider;
     static private NotificationChannelController sNotificationChannelController;
     static private CellularNetworkValidator sCellularNetworkValidator;
 
@@ -285,9 +287,15 @@
 
                 sNotificationChannelController = new NotificationChannelController(context);
 
-                for (int i = 0; i < numPhones; i++) {
-                    sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            Looper.myLooper(), sPhones[i], featureFlags);
+                if (featureFlags.supportNetworkProvider()) {
+                    // Create the TelephonyNetworkProvider instance, which is a singleton.
+                    sTelephonyNetworkProvider = new TelephonyNetworkProvider(Looper.myLooper(),
+                            context, featureFlags);
+                } else {
+                    for (int i = 0; i < numPhones; i++) {
+                        sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+                                Looper.myLooper(), sPhones[i], featureFlags);
+                    }
                 }
             }
         }
@@ -312,7 +320,10 @@
 
             sPhones = copyOf(sPhones, activeModemCount);
             sCommandsInterfaces = copyOf(sCommandsInterfaces, activeModemCount);
-            sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+
+            if (!sFeatureFlags.supportNetworkProvider()) {
+                sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+            }
 
             int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
             for (int i = prevActiveModemCount; i < activeModemCount; i++) {
@@ -324,8 +335,11 @@
                         PackageManager.FEATURE_TELEPHONY_IMS)) {
                     sPhones[i].createImsPhone();
                 }
-                sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                        Looper.myLooper(), sPhones[i], sFeatureFlags);
+
+                if (!sFeatureFlags.supportNetworkProvider()) {
+                    sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+                            Looper.myLooper(), sPhones[i], sFeatureFlags);
+                }
             }
         }
     }
@@ -393,6 +407,10 @@
         }
     }
 
+    public static TelephonyNetworkProvider getNetworkProvider() {
+        return sTelephonyNetworkProvider;
+    }
+
     /**
      * Get the network factory associated with a given phone ID.
      * @param phoneId the phone id
@@ -579,13 +597,22 @@
             pw.flush();
             pw.println("++++++++++++++++++++++++++++++++");
 
-            sTelephonyNetworkFactories[i].dump(fd, pw, args);
+            if (!sFeatureFlags.supportNetworkProvider()) {
+                sTelephonyNetworkFactories[i].dump(fd, pw, args);
+            }
 
             pw.flush();
             pw.decreaseIndent();
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        pw.increaseIndent();
+        if (sFeatureFlags.supportNetworkProvider()) {
+            sTelephonyNetworkProvider.dump(fd, pw, args);
+        }
+        pw.decreaseIndent();
+        pw.println("++++++++++++++++++++++++++++++++");
+
         pw.println("UiccController:");
         pw.increaseIndent();
         try {
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index da20639..b4db14c 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -466,7 +466,7 @@
         RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
                 result, mDefaultWorkSource);
         if (DBG) {
-            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " " + modemId);
         }
         try {
             proxy.setPreferredDataModem(rr.mSerial, modemId);
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 2f0c0ff..e66ea69 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -100,6 +100,7 @@
 import com.android.internal.telephony.data.DataRetryManager.DataRetryEntry;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
 import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
@@ -271,6 +272,9 @@
     /** Event for response to data network validation request. */
     private static final int EVENT_DATA_NETWORK_VALIDATION_RESPONSE = 29;
 
+    /** Event for preferred data subscription changed. */
+    private static final int EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED = 30;
+
     /** Invalid context id. */
     private static final int INVALID_CID = -1;
 
@@ -591,6 +595,10 @@
     @NonNull
     private final DataNetworkController mDataNetworkController;
 
+    /** Phone switcher which is responsible to determine which phone to route network request. */
+    @NonNull
+    private final PhoneSwitcher mPhoneSwitcher;
+
     /** Data network controller callback. */
     @NonNull
     private final DataNetworkController.DataNetworkControllerCallback
@@ -676,6 +684,13 @@
     /** Whether the current data network is congested. */
     private boolean mCongested = false;
 
+    /**
+     * Whether the current data network is on preferred data modem.
+     *
+     * @see PhoneSwitcher#getPreferredDataPhoneId()
+     */
+    private boolean mOnPreferredDataPhone;
+
     /** The network requests associated with this data network */
     @NonNull
     private final NetworkRequestList mAttachedNetworkRequestList =
@@ -815,6 +830,12 @@
     private PreciseDataConnectionState mPreciseDataConnectionState;
 
     /**
+     * Callback to listen event from {@link PhoneSwitcher}.
+     */
+    @NonNull
+    private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -1027,6 +1048,8 @@
         mAccessNetworksManager = phone.getAccessNetworksManager();
         mVcnManager = mPhone.getContext().getSystemService(VcnManager.class);
         mDataNetworkController = phone.getDataNetworkController();
+        mPhoneSwitcher = PhoneSwitcher.getInstance();
+        mOnPreferredDataPhone = phone.getPhoneId() == mPhoneSwitcher.getPreferredDataPhoneId();
         mDataNetworkControllerCallback = new DataNetworkController.DataNetworkControllerCallback(
                 getHandler()::post) {
             @Override
@@ -1156,14 +1179,25 @@
             configBuilder.setNat64DetectionEnabled(false);
         }
 
-        final NetworkFactory factory = PhoneFactory.getNetworkFactory(
-                mPhone.getPhoneId());
-        final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
+        NetworkProvider provider;
+        if (mFlags.supportNetworkProvider()) {
+            provider = PhoneFactory.getNetworkProvider();
+        } else {
+            final NetworkFactory factory = PhoneFactory.getNetworkFactory(
+                    mPhone.getPhoneId());
+            provider = (null == factory) ? null : factory.getProvider();
+        }
 
-        mNetworkScore = new NetworkScore.Builder()
-               .setKeepConnectedReason(isHandoverInProgress()
+        NetworkScore.Builder builder = new NetworkScore.Builder()
+                .setKeepConnectedReason(isHandoverInProgress()
                         ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
-                        : NetworkScore.KEEP_CONNECTED_NONE).build();
+                        : NetworkScore.KEEP_CONNECTED_NONE);
+        if (mFlags.supportNetworkProvider()) {
+            builder.setTransportPrimary(mOnPreferredDataPhone);
+        }
+        mNetworkScore = builder.build();
+        logl("mNetworkScore: isPrimary=" + mNetworkScore.isTransportPrimary()
+                + ", keepConnectedReason=" + mNetworkScore.getKeepConnectedReason());
 
         return new TelephonyNetworkAgent(mPhone, getHandler().getLooper(), this,
                 mNetworkScore, configBuilder.build(), provider,
@@ -1225,6 +1259,16 @@
             mDataNetworkController.getDataSettingsManager()
                     .registerCallback(mDataSettingsManagerCallback);
 
+            if (mFlags.supportNetworkProvider()) {
+                mPhoneSwitcherCallback = new PhoneSwitcherCallback(Runnable::run) {
+                    @Override
+                    public void onPreferredDataPhoneIdChanged(int phoneId) {
+                        sendMessage(EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED, phoneId, 0);
+                    }
+                };
+                mPhoneSwitcher.registerCallback(mPhoneSwitcherCallback);
+            }
+
             mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
                     getHandler(), EVENT_DISPLAY_INFO_CHANGED, null);
             mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
@@ -1318,6 +1362,9 @@
             mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
             mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
                     getHandler());
+            if (mFlags.supportNetworkProvider()) {
+                mPhoneSwitcher.unregisterCallback(mPhoneSwitcherCallback);
+            }
             mDataNetworkController.getDataSettingsManager()
                     .unregisterCallback(mDataSettingsManagerCallback);
             mRil.unregisterForPcoData(getHandler());
@@ -1351,13 +1398,13 @@
                 }
                 case EVENT_ATTACH_NETWORK_REQUEST: {
                     onAttachNetworkRequests((NetworkRequestList) msg.obj);
-                    updateNetworkScore(isHandoverInProgress());
+                    updateNetworkScore();
                     break;
                 }
                 case EVENT_DETACH_NETWORK_REQUEST: {
                     onDetachNetworkRequest((TelephonyNetworkRequest) msg.obj,
                             msg.arg1 != 0 /* shouldRetry */);
-                    updateNetworkScore(isHandoverInProgress());
+                    updateNetworkScore();
                     break;
                 }
                 case EVENT_DETACH_ALL_NETWORK_REQUESTS: {
@@ -1428,6 +1475,12 @@
                     // handle the resultCode in response for the request.
                     handleDataNetworkValidationRequestResultCode(msg.arg1 /* resultCode */);
                     break;
+                case EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED:
+                    mOnPreferredDataPhone = mPhone.getPhoneId() == msg.arg1;
+                    logl("Preferred data phone id changed to " + msg.arg1
+                            + ", mOnPreferredDataPhone=" + mOnPreferredDataPhone);
+                    updateNetworkScore();
+                    break;
                 default:
                     loge("Unhandled event " + eventToString(msg.what));
                     break;
@@ -3314,6 +3367,12 @@
         return mLinkStatus;
     }
 
+    /**
+     * Update the network score and report to connectivity service if necessary.
+     */
+    private void updateNetworkScore() {
+        updateNetworkScore(isHandoverInProgress());
+    }
 
     /**
      * Update the network score and report to connectivity service if necessary.
@@ -3323,10 +3382,18 @@
     private void updateNetworkScore(boolean keepConnectedForHandover) {
         int connectedReason = keepConnectedForHandover
                 ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER : NetworkScore.KEEP_CONNECTED_NONE;
-        if (mNetworkScore.getKeepConnectedReason() != connectedReason) {
-            mNetworkScore = new NetworkScore.Builder()
-                    .setKeepConnectedReason(connectedReason).build();
+        if (mNetworkScore.getKeepConnectedReason() != connectedReason
+                || (mFlags.supportNetworkProvider()
+                && mNetworkScore.isTransportPrimary() != mOnPreferredDataPhone)) {
+            NetworkScore.Builder builder = new NetworkScore.Builder()
+                    .setKeepConnectedReason(connectedReason);
+            if (mFlags.supportNetworkProvider()) {
+                builder.setTransportPrimary(mOnPreferredDataPhone);
+            }
+            mNetworkScore = builder.build();
             mNetworkAgent.sendNetworkScore(mNetworkScore);
+            logl("updateNetworkScore: isPrimary=" + mNetworkScore.isTransportPrimary()
+                    + ", keepConnectedForHandover=" + keepConnectedForHandover);
         }
     }
 
@@ -4055,12 +4122,14 @@
         pw.println("Tag: " + name());
         pw.increaseIndent();
         pw.println("mSubId=" + mSubId);
+        pw.println("mOnPreferredDataPhone=" + mOnPreferredDataPhone);
         pw.println("mTransport=" + AccessNetworkConstants.transportTypeToString(mTransport));
         pw.println("mLastKnownDataNetworkType=" + TelephonyManager
                 .getNetworkTypeName(mLastKnownDataNetworkType));
         pw.println("WWAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
         pw.println("WLAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
         pw.println("mNetworkScore=" + mNetworkScore);
+        pw.println("keepConnectedReason=" + mNetworkScore.getKeepConnectedReason());
         pw.println("mDataAllowedReason=" + mDataAllowedReason);
         pw.println("mPduSessionId=" + mPduSessionId);
         pw.println("mDataProfile=" + mDataProfile);
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 2572e2f..1b0af47 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -2516,6 +2516,30 @@
     }
 
     private void onRemoveNetworkRequest(@NonNull TelephonyNetworkRequest request) {
+        if (mFeatureFlags.supportNetworkProvider()) {
+            if (!mAllNetworkRequestList.remove(request)) {
+                loge("onRemoveNetworkRequest: Network request does not exist. " + request);
+                return;
+            }
+
+            if (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+                mImsThrottleCounter.addOccurrence();
+                mLastReleasedImsRequestCapabilities = request.getCapabilities();
+                mLastImsOperationIsRelease = true;
+            }
+
+            if (request.getAttachedNetwork() != null) {
+                request.getAttachedNetwork().detachNetworkRequest(
+                        request, false /* shouldRetry */);
+            }
+
+            request.setState(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
+            request.setEvaluation(null);
+
+            log("onRemoveNetworkRequest: Removed " + request);
+            return;
+        }
+
         // The request generated from telephony network factory does not contain the information
         // the original request has, for example, attached data network. We need to find the
         // original one.
@@ -2536,7 +2560,7 @@
 
         if (networkRequest.getAttachedNetwork() != null) {
             networkRequest.getAttachedNetwork().detachNetworkRequest(
-                        networkRequest, false /* shouldRetry */);
+                    networkRequest, false /* shouldRetry */);
         }
         log("onRemoveNetworkRequest: Removed " + networkRequest);
     }
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index f1f7b49..1005bb7 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -30,6 +30,7 @@
 
 import static java.util.Arrays.copyOf;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -63,6 +64,7 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -183,6 +185,27 @@
         }
     }
 
+    /**
+     * Callback from PhoneSwitcher
+     */
+    public static class PhoneSwitcherCallback extends DataCallback {
+        /**
+         * Constructor
+         *
+         * @param executor The executor of the callback.
+         */
+        public PhoneSwitcherCallback(@NonNull @CallbackExecutor Executor executor) {
+            super(executor);
+        }
+
+        /**
+         * Called when preferred data phone id changed.
+         *
+         * @param phoneId The phone id of the preferred data.
+         */
+        public void onPreferredDataPhoneIdChanged(int phoneId) {}
+    }
+
     @NonNull
     private final NetworkRequestList mNetworkRequestList = new NetworkRequestList();
     protected final RegistrantList mActivePhoneRegistrants;
@@ -261,6 +284,10 @@
 
     private ISetOpportunisticDataCallback mSetOpptSubCallback;
 
+    /** Phone switcher callbacks. */
+    @NonNull
+    private final Set<PhoneSwitcherCallback> mPhoneSwitcherCallbacks = new ArraySet<>();
+
     private static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
     protected static final int EVENT_SUBSCRIPTION_CHANGED         = 102;
     private static final int EVENT_REQUEST_NETWORK                = 103;
@@ -468,6 +495,24 @@
         }
     }
 
+    /**
+     * Register the callback for receiving information from {@link PhoneSwitcher}.
+     *
+     * @param callback The callback.
+     */
+    public void registerCallback(@NonNull PhoneSwitcherCallback callback) {
+        mPhoneSwitcherCallbacks.add(callback);
+    }
+
+    /**
+     * Unregister the callback for receiving information from {@link PhoneSwitcher}.
+     *
+     * @param callback The callback.
+     */
+    public void unregisterCallback(@NonNull PhoneSwitcherCallback callback) {
+        mPhoneSwitcherCallbacks.remove(callback);
+    }
+
     private void evaluateIfImmediateDataSwitchIsNeeded(String evaluationReason, int switchReason) {
         if (onEvaluate(REQUESTS_UNCHANGED, evaluationReason)) {
             logDataSwitchEvent(mPreferredDataSubId.get(),
@@ -607,33 +652,35 @@
 
         mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
 
-        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
-                .setNetworkSpecifier(new MatchAllNetworkSpecifier());
-        TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
-                .forEach(builder::addCapability);
+        if (!mFlags.supportNetworkProvider()) {
+            final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
+                    .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+            TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+                    .forEach(builder::addCapability);
 
-        if (mFlags.satelliteInternet()) {
-            // TODO: b/328622096 remove the try/catch
-            try {
-                builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
-            } catch (IllegalArgumentException exception) {
-                loge("TRANSPORT_SATELLITE is not supported.");
+            if (mFlags.satelliteInternet()) {
+                // TODO: b/328622096 remove the try/catch
+                try {
+                    builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+                } catch (IllegalArgumentException exception) {
+                    loge("TRANSPORT_SATELLITE is not supported.");
+                }
             }
-        }
 
-        NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
-                builder.build(), this);
-        // we want to see all requests
-        networkFactory.registerIgnoringScore();
+            NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
+                    builder.build(), this);
+            // we want to see all requests
+            networkFactory.registerIgnoringScore();
+        }
 
         updateHalCommandToUse();
 
@@ -1053,11 +1100,12 @@
         return false;
     }
 
+    // TODO: Remove after removing TelephonyNetworkFactory
     private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
         private final PhoneSwitcher mPhoneSwitcher;
         public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
                 NetworkCapabilities nc, PhoneSwitcher ps) {
-            super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
+            super(l, c, "PhoneSwitcherNetworkRequestListener", nc);
             mPhoneSwitcher = ps;
         }
 
@@ -1078,7 +1126,13 @@
         }
     }
 
-    private void onRequestNetwork(NetworkRequest networkRequest) {
+    /**
+     * Called when receiving a network request.
+     *
+     * @param networkRequest The network request.
+     */
+    // TODO: Transform to TelephonyNetworkRequest after removing TelephonyNetworkFactory
+    public void onRequestNetwork(@NonNull NetworkRequest networkRequest) {
         TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
                 networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
         if (!mNetworkRequestList.contains(telephonyNetworkRequest)) {
@@ -1087,7 +1141,13 @@
         }
     }
 
-    private void onReleaseNetwork(NetworkRequest networkRequest) {
+    /**
+     * Called when releasing a network request.
+     *
+     * @param networkRequest The network request to release.
+     */
+    // TODO: Transform to TelephonyNetworkRequest after removing TelephonyNetworkFactory
+    public void onReleaseNetwork(@NonNull NetworkRequest networkRequest) {
         TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
                 networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
         if (mNetworkRequestList.remove(telephonyNetworkRequest)) {
@@ -1101,18 +1161,6 @@
         mDefaultNetworkCallback.mSwitchReason = reason;
     }
 
-    private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
-        // Request network for MMS will temporary disable the network on default data subscription,
-        // this only happen on multi-sim device.
-        if (mActiveModemCount > 1 && networkRequest.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
-            onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
-            onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_START;
-            TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
-        }
-    }
-
     private void collectReleaseNetworkMetrics(NetworkRequest networkRequest) {
         // Release network for MMS will recover the network on default data subscription, this only
         // happen on multi-sim device.
@@ -1561,6 +1609,7 @@
      * If preferred phone changes, or phone activation status changes, registrants
      * will be notified.
      */
+    // TODO: Remove after removing TelephonyNetworkFactory
     public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
         Registrant r = new Registrant(h, what, o);
         mActivePhoneRegistrants.add(r);
@@ -1982,6 +2031,10 @@
         // Notify all registrants
         mActivePhoneRegistrants.notifyRegistrants();
         notifyPreferredDataSubIdChanged();
+        if (mFlags.supportNetworkProvider()) {
+            mPhoneSwitcherCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onPreferredDataPhoneIdChanged(phoneId)));
+        }
     }
 
     private boolean isPhoneIdValidForRetry(int phoneId) {
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java
new file mode 100644
index 0000000..63edefa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2024 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.internal.telephony.data;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Map;
+
+/**
+ * TelephonyNetworkProvider is a singleton network provider responsible for providing all
+ * telephony related networks including networks on cellular and IWLAN across all active SIMs.
+ */
+public class TelephonyNetworkProvider extends NetworkProvider implements NetworkOfferCallback {
+
+    public final String LOG_TAG = "TNP";
+
+    /** Android feature flags */
+    @NonNull
+    private final FeatureFlags mFlags;
+
+    /** The event handler */
+    @NonNull
+    private final Handler mHandler;
+
+    /** Phone switcher responsible to determine request routing on dual-SIM device */
+    @NonNull
+    private final PhoneSwitcher mPhoneSwitcher;
+
+    /** Network requests map. Key is the network request, value is the phone id it applies to. */
+    private final Map<TelephonyNetworkRequest, Integer> mNetworkRequests = new ArrayMap<>();
+
+    /** Persisted log */
+    @NonNull
+    private final LocalLog mLocalLog = new LocalLog(256);
+
+    /**
+     * Constructor
+     *
+     * @param looper The looper for event handling
+     * @param context The context
+     * @param featureFlags Android feature flags
+     */
+    public TelephonyNetworkProvider(@NonNull Looper looper, @NonNull Context context,
+                                    @NonNull FeatureFlags featureFlags) {
+        super(context, looper, TelephonyNetworkProvider.class.getSimpleName());
+
+        mFlags = featureFlags;
+        mHandler = new Handler(looper);
+        mPhoneSwitcher = PhoneSwitcher.getInstance();
+
+        // Register for subscription changed event.
+        context.getSystemService(SubscriptionManager.class)
+                .addOnSubscriptionsChangedListener(mHandler::post,
+                        new SubscriptionManager.OnSubscriptionsChangedListener() {
+                        @Override
+                        public void onSubscriptionsChanged() {
+                            logl("Subscription changed.");
+                            reevaluateNetworkRequests("subscription changed");
+                        }});
+
+        // Register for preferred data changed event
+        mPhoneSwitcher.registerCallback(new PhoneSwitcherCallback(mHandler::post) {
+                    @Override
+                    public void onPreferredDataPhoneIdChanged(int phoneId) {
+                        logl("Preferred data sub phone id changed to " + phoneId);
+                        reevaluateNetworkRequests("Preferred data subscription changed");
+                    }
+                });
+
+        // Register the provider and tell connectivity service what network offer telephony can
+        // provide
+        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        if (cm != null) {
+            cm.registerNetworkProvider(this);
+            NetworkCapabilities caps = makeNetworkFilter();
+            registerNetworkOffer(new NetworkScore.Builder().build(), caps, mHandler::post, this);
+            logl("registerNetworkOffer: " + caps);
+        }
+    }
+
+    /**
+     * Get the phone id for the network request.
+     *
+     * @param request The network request
+     * @return The id of the phone where the network request should route to. If the network request
+     * can't be applied to any phone, {@link SubscriptionManager#INVALID_PHONE_INDEX} will be
+     * returned.
+     */
+    private int getPhoneIdForNetworkRequest(@NonNull TelephonyNetworkRequest request) {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            int phoneId = phone.getPhoneId();
+            if (mPhoneSwitcher.shouldApplyNetworkRequest(request, phoneId)) {
+                // Return here because by design the network request can be only applied to *one*
+                // phone. It's not possible to have two DataNetworkController to attempt to setup
+                // data call for the same network request.
+                return phoneId;
+            }
+        }
+
+        return SubscriptionManager.INVALID_PHONE_INDEX;
+    }
+
+    /**
+     * Called when receiving a network request from connectivity service. This is the entry point
+     * that a network request arrives telephony.
+     *
+     * @param request The network request
+     */
+    @Override
+    public void onNetworkNeeded(@NonNull NetworkRequest request) {
+        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(request, mFlags);
+        if (mNetworkRequests.containsKey(networkRequest)) {
+            loge("Duplicate network request " + networkRequest);
+            return;
+        }
+
+        mPhoneSwitcher.onRequestNetwork(request);
+
+        // Check with PhoneSwitcher to see where to route the request.
+        int phoneId = getPhoneIdForNetworkRequest(networkRequest);
+        if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+            logl("onNetworkNeeded: phoneId=" + phoneId + ", " + networkRequest);
+            PhoneFactory.getPhone(phoneId).getDataNetworkController()
+                    .addNetworkRequest(networkRequest);
+        } else {
+            logl("onNetworkNeeded: Not applied. " + networkRequest);
+        }
+
+        mNetworkRequests.put(networkRequest, phoneId);
+    }
+
+    /**
+     * Called when connectivity service remove the network request. Note this will not result in
+     * network tear down. Even there is no network request attached to the network, telephony still
+     * relies on {@link NetworkAgent#onNetworkUnwanted()} to tear down the network.
+     *
+     * @param request The released network request
+     *
+     * @see TelephonyNetworkAgent#onNetworkUnwanted()
+     */
+    @Override
+    public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+        TelephonyNetworkRequest networkRequest = mNetworkRequests.keySet().stream()
+                .filter(r -> r.getNativeNetworkRequest().equals(request))
+                .findFirst()
+                .orElse(null);
+        if (networkRequest == null) {
+            loge("onNetworkUnneeded: Cannot find " + request);
+            return;
+        }
+
+        mPhoneSwitcher.onReleaseNetwork(request);
+        int phoneId = mNetworkRequests.remove(networkRequest);
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null) {
+            logl("onNetworkUnneeded: phoneId=" + phoneId + ", " + networkRequest);
+            // Remove the network request from network controller. Note this will not result
+            // in disconnecting the data network.
+            phone.getDataNetworkController().removeNetworkRequest(networkRequest);
+        } else {
+            loge("onNetworkUnneeded: Unable to get phone. phoneId=" + phoneId);
+        }
+    }
+
+    /**
+     * Re-evaluate the existing networks and re-apply to the applicable phone.
+     *
+     * @param reason The reason for re-evaluating network request. Note this can be only used for
+     * debugging message purposes.
+     */
+    private void reevaluateNetworkRequests(@NonNull String reason) {
+        logl("reevaluateNetworkRequests: " + reason + ".");
+        mNetworkRequests.forEach((request, oldPhoneId) -> {
+            int newPhoneId = getPhoneIdForNetworkRequest(request);
+            if (newPhoneId != oldPhoneId) {
+                // We need to move the request from old phone to the new phone. This can happen
+                // when the user changes the default data subscription.
+
+                if (oldPhoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+                    PhoneFactory.getPhone(oldPhoneId).getDataNetworkController()
+                            .removeNetworkRequest(request);
+                }
+
+                if (newPhoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+                    PhoneFactory.getPhone(newPhoneId).getDataNetworkController()
+                            .addNetworkRequest(request);
+                }
+
+                logl("Request moved. phoneId " + oldPhoneId + " -> " + newPhoneId + " " + request);
+                mNetworkRequests.put(request, newPhoneId);
+            }
+        });
+    }
+
+    /**
+     * @return The maximal network capabilities that telephony can support.
+     */
+    @NonNull
+    private NetworkCapabilities makeNetworkFilter() {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
+                // Ideally TelephonyNetworkProvider should only accept TelephonyNetworkSpecifier,
+                // but this network provider is a singleton across all SIMs, and
+                // TelephonyNetworkSpecifier can't accept more than one subscription id, so we let
+                // the provider accepts all different kinds NetworkSpecifier.
+                .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+                .forEach(builder::addCapability);
+
+        // TODO: b/328622096 remove the try/catch
+        try {
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        } catch (IllegalArgumentException exception) {
+            log("TRANSPORT_SATELLITE is not supported.");
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Log debug message to logcat.
+     *
+     * @param s The debug message to log
+     */
+    private void log(@NonNull String s) {
+        Rlog.d(LOG_TAG, s);
+    }
+
+    /**
+     * Log error debug messages to logcat.
+     * @param s The error debug messages
+     */
+    private void loge(@NonNull String s) {
+        Rlog.e(LOG_TAG, s);
+    }
+
+    /**
+     * Log to logcat and persisted local log.
+     *
+     * @param s The debug message to log
+     */
+    private void logl(@NonNull String s) {
+        log(s);
+        mLocalLog.log(s);
+    }
+
+    /**
+     * Dump the state of telephony network provider.
+     *
+     * @param fd File descriptor
+     * @param writer Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println("TelephonyNetworkProvider:");
+        pw.increaseIndent();
+
+        pw.println("mPreferredDataPhoneId=" + mPhoneSwitcher.getPreferredDataPhoneId());
+        int defaultDataSubId = SubscriptionManagerService.getInstance().getDefaultDataSubId();
+        pw.println("DefaultDataSubId=" + defaultDataSubId);
+        pw.println("DefaultDataPhoneId=" + SubscriptionManagerService.getInstance()
+                .getPhoneId(defaultDataSubId));
+
+        pw.println("Registered capabilities: " + makeNetworkFilter());
+        pw.println("Network requests:");
+        pw.increaseIndent();
+        for (Phone phone : PhoneFactory.getPhones()) {
+            pw.println("Phone " + phone.getPhoneId() + ":");
+            pw.increaseIndent();
+            mNetworkRequests.forEach((request, phoneId) -> {
+                if (phoneId == phone.getPhoneId()) {
+                    pw.println(request);
+                }
+            });
+            pw.decreaseIndent();
+        }
+        pw.println("Not applied requests:");
+        pw.increaseIndent();
+        mNetworkRequests.forEach((request, phoneId) -> {
+            if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
+                pw.println(request);
+            }
+        });
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+        pw.println();
+        pw.println("Local logs:");
+        pw.increaseIndent();
+        mLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
index a43367e..ca1ca21 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -488,7 +488,7 @@
      *
      * @param evaluation The data evaluation result.
      */
-    public void setEvaluation(@NonNull DataEvaluation evaluation) {
+    public void setEvaluation(@Nullable DataEvaluation evaluation) {
         mEvaluation = evaluation;
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 38b4f77..36ac992 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -580,6 +580,7 @@
         mNullCipherNotifier = Mockito.mock(NullCipherNotifier.class);
 
         doReturn(true).when(mFeatureFlags).minimalTelephonyCdmCheck();
+        doReturn(true).when(mFeatureFlags).supportNetworkProvider();
 
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index 499c1f5..0c2faa1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -159,7 +159,6 @@
     private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23;
 
     // Mocked classes
-    private PhoneSwitcher mMockedPhoneSwitcher;
     protected ISub mMockedIsub;
     private DataNetworkControllerCallback mMockedDataNetworkControllerCallback;
     private DataRetryManagerCallback mMockedDataRetryManagerCallback;
@@ -855,7 +854,6 @@
     public void setUp() throws Exception {
         logd("DataNetworkControllerTest +Setup!");
         super.setUp(getClass().getSimpleName());
-        mMockedPhoneSwitcher = Mockito.mock(PhoneSwitcher.class);
         mMockedIsub = Mockito.mock(ISub.class);
         mMockedImsManager = mContext.getSystemService(ImsManager.class);
         mMockedImsMmTelManager = Mockito.mock(ImsMmTelManager.class);
@@ -878,7 +876,6 @@
         mMockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mMockedWlanDataServiceManager);
 
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mMockedPhoneSwitcher);
         doReturn(1).when(mMockedIsub).getDefaultDataSubId();
         doReturn(mMockedIsub).when(mIBinder).queryLocalInterface(anyString());
         doReturn(mPhone).when(mPhone).getImsPhone();
@@ -4908,14 +4905,10 @@
 
         NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
                 ConnectivityManager.TYPE_MOBILE, 0, NetworkRequest.Type.REQUEST);
-
-        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
-                nativeNetworkRequest, mPhone, mFeatureFlags));
-        processAllMessages();
-
-        // Intentionally create a new telephony request with the original native network request.
         TelephonyNetworkRequest request = new TelephonyNetworkRequest(
                 nativeNetworkRequest, mPhone, mFeatureFlags);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
 
         mDataNetworkControllerUT.removeNetworkRequest(request);
         processAllFutureMessages();
@@ -4936,7 +4929,7 @@
         processAllMessages();
 
         // this slot is 0, modem preferred on slot 1
-        doReturn(1).when(mMockedPhoneSwitcher).getPreferredDataPhoneId();
+        doReturn(1).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
         // Simulate telephony network factory remove request due to switch.
         mDataNetworkControllerUT.removeNetworkRequest(request);
@@ -4949,7 +4942,7 @@
     @Test
     public void testSetupDataOnNonDds() throws Exception {
         // this slot is 0, modem preferred on slot 1
-        doReturn(1).when(mMockedPhoneSwitcher).getPreferredDataPhoneId();
+        doReturn(1).when(mPhoneSwitcher).getPreferredDataPhoneId();
         TelephonyNetworkRequest request = createNetworkRequest(
                 NetworkCapabilities.NET_CAPABILITY_MMS);
 
@@ -5475,4 +5468,18 @@
         assertThat(mDataNetworkControllerUT.getInternetEvaluation(true/*ignoreExistingNetworks*/)
                 .containsDisallowedReasons()).isTrue();
     }
+
+    @Test
+    public void testRemoveNetworkRequestClearState() throws Exception {
+        TelephonyNetworkRequest request = createNetworkRequest(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        assertThat(request.getState()).isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
+
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        processAllMessages();
+        assertThat(request.getState()).isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
index 8be0f8b..6539ca9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -86,6 +87,7 @@
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
 import com.android.internal.telephony.test.SimulatedCommands;
 
@@ -238,14 +240,14 @@
     // Mocked classes
     private DataNetworkCallback mDataNetworkCallback;
     private DataCallSessionStats mDataCallSessionStats;
-    private PhoneSwitcher mMockedPhoneSwitcher;
-
     private final NetworkRegistrationInfo mIwlanNetworkRegistrationInfo =
             new NetworkRegistrationInfo.Builder()
                     .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
                     .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                     .build();
 
+    private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
     private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid) {
         setSuccessfulSetupDataResponse(dsm, cid, Collections.emptyList(), null);
     }
@@ -379,14 +381,13 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        doReturn(1).when(mPhone).getSubId();
         doReturn(mImsPhone).when(mPhone).getImsPhone();
         doReturn(mImsCT).when(mImsPhone).getCallTracker();
         doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
 
         mDataNetworkCallback = Mockito.mock(DataNetworkCallback.class);
         mDataCallSessionStats = Mockito.mock(DataCallSessionStats.class);
-        mMockedPhoneSwitcher = Mockito.mock(PhoneSwitcher.class);
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mMockedPhoneSwitcher);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArguments()[0]).run();
             return null;
@@ -2662,4 +2663,35 @@
         assertThat(mDataNetworkUT.getNetworkCapabilities()
                 .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
     }
+
+    @Test
+    public void testPrimaryTransport() throws Exception {
+        doReturn(0).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        setupDataNetwork();
+        TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+        replaceInstance(DataNetwork.class, "mNetworkAgent",
+                mDataNetworkUT, mockNetworkAgent);
+
+        ArgumentCaptor<PhoneSwitcherCallback> callbackCaptor =
+                ArgumentCaptor.forClass(PhoneSwitcherCallback.class);
+        verify(mPhoneSwitcher).registerCallback(callbackCaptor.capture());
+        mPhoneSwitcherCallback = callbackCaptor.getValue();
+
+        // Switch the preferred data subscription to another.
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(1);
+        processAllMessages();
+
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+        verify(mockNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        assertThat(networkScoreCaptor.getValue().isTransportPrimary()).isFalse();
+        clearInvocations(mockNetworkAgent);
+
+        // Switch back
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(0);
+        processAllMessages();
+
+        verify(mockNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        assertThat(networkScoreCaptor.getValue().isTransportPrimary()).isTrue();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
index a820ec7..f7990b9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -52,18 +52,15 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
@@ -75,8 +72,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import androidx.test.filters.SmallTest;
-
 import com.android.ims.ImsException;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CommandException;
@@ -140,9 +135,6 @@
 
     private PhoneSwitcher mPhoneSwitcherUT;
     private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
-    private ConnectivityManager mConnectivityManager;
-    // The messenger of PhoneSwitcher used to receive network requests.
-    private Messenger mNetworkProviderMessenger = null;
     private Map<Integer, DataSettingsManager.DataSettingsManagerCallback>
             mDataSettingsManagerCallbacks;
     private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -211,8 +203,6 @@
     public void tearDown() throws Exception {
         mPhoneSwitcherUT = null;
         mSubChangedListener = null;
-        mConnectivityManager = null;
-        mNetworkProviderMessenger = null;
         mTelephonyDisplayInfo = null;
         super.tearDown();
     }
@@ -221,7 +211,6 @@
      * Test that a single phone case results in our phone being active and the RIL called
      */
     @Test
-    @SmallTest
     public void testRegister() throws Exception {
         initialize();
 
@@ -468,7 +457,6 @@
     }
 
     @Test
-    @SmallTest
     public void testAutoDataSwitch_exemptPingTest() throws Exception {
         initialize();
 
@@ -506,7 +494,6 @@
      * - don't switch phones when in emergency mode
      */
     @Test
-    @SmallTest
     public void testPrioritization() throws Exception {
         initialize();
 
@@ -541,7 +528,6 @@
      * wins (ie, switch to wifi).
      */
     @Test
-    @SmallTest
     public void testHigherPriorityDefault() throws Exception {
         initialize();
 
@@ -574,7 +560,6 @@
      * active one.
      */
     @Test
-    @SmallTest
     public void testSetPreferredData() throws Exception {
         initialize();
 
@@ -620,7 +605,6 @@
      * 3. CBRS requests OR Auto switch requests - only one case applies at a time
      */
     @Test
-    @SmallTest
     public void testSetPreferredDataCasePriority_CbrsWaitsForVoiceCall() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -674,7 +658,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredData_NoAutoSwitchWhenCbrs() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -728,7 +711,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredDataModemCommand() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -827,7 +809,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredDataWithValidation() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -888,7 +869,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnLte_shouldSwitchDds() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -916,7 +896,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallDialingOnLte_shouldSwitchDds()
             throws Exception {
         initialize();
@@ -950,7 +929,6 @@
         assertEquals(1, mPhoneSwitcherUT.getPreferredDataPhoneId());
     }
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallIncomingOnLte_shouldSwitchDds()
             throws Exception {
         initialize();
@@ -979,7 +957,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnWlan_shouldNotSwitchDds() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -1006,7 +983,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnCrossSIM_HandoverToLTE() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -1169,7 +1145,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNetworkRequestOnNonDefaultData() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1194,7 +1169,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideSuccessBeforeCallStarts() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1215,7 +1189,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideNoDdsChange() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1235,7 +1208,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideEndSuccess() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1273,7 +1245,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideEcbmStartEnd() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1323,7 +1294,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideNoCallStart() throws Exception {
         PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1355,7 +1325,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideMultipleOverrideRequests() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1406,7 +1375,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredDataCallback() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1581,7 +1549,6 @@
     }
 
     @Test
-    @SmallTest
     public void testMultiSimConfigChange() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         mActiveModemCount = 1;
@@ -1609,7 +1576,6 @@
     }
 
     @Test
-    @SmallTest
     public void testValidationOffSwitch_shouldSwitchOnNetworkAvailable() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1650,7 +1616,6 @@
     }
 
     @Test
-    @SmallTest
     public void testValidationOffSwitch_shouldSwitchOnTimeOut() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1773,7 +1738,6 @@
     }
 
     @Test
-    @SmallTest
     public void testRegisterForImsRegistrationCallback() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -1801,7 +1765,6 @@
     }
 
     @Test
-    @SmallTest
     public void testReceivingImsRegistrationTech() throws Exception {
         doReturn(true).when(mFeatureFlags).changeMethodOfObtainingImsRegistrationRadioTech();
 
@@ -1882,9 +1845,6 @@
         doReturn(true).when(mPhone2).isDataAllowed();
         doReturn(true).when(mDataSettingsManager2).isDataEnabled();
 
-        // 3.1 No default network
-        doReturn(null).when(mConnectivityManager).getNetworkCapabilities(any());
-
         mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
     }
 
@@ -2040,7 +2000,6 @@
         initializeSubControllerMock();
         initializeCommandInterfacesMock();
         initializeTelRegistryMock();
-        initializeConnManagerMock();
         initializeConfigMock();
 
         mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper(),
@@ -2154,21 +2113,6 @@
      * Capture mNetworkProviderMessenger so that testing can request or release
      * network requests on PhoneSwitcher.
      */
-    private void initializeConnManagerMock() {
-        mConnectivityManager = (ConnectivityManager)
-                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-
-        doAnswer(invocation -> {
-            mNetworkProviderMessenger =
-                    ((NetworkProvider) invocation.getArgument(0)).getMessenger();
-            return null;
-        }).when(mConnectivityManager).registerNetworkProvider(any());
-    }
-
-    /**
-     * Capture mNetworkProviderMessenger so that testing can request or release
-     * network requests on PhoneSwitcher.
-     */
     private void initializeSubControllerMock() throws Exception {
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
         doReturn(mDefaultDataSub).when(mMockedIsub).getDefaultDataSubId();
@@ -2265,13 +2209,7 @@
         NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
                 0, NetworkRequest.Type.REQUEST);
 
-        Message message = Message.obtain();
-        message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
-        message.arg1 = score;
-        message.obj = networkRequest;
-        mNetworkProviderMessenger.send(message);
-        processAllMessages();
-
+        mPhoneSwitcherUT.onRequestNetwork(networkRequest);
         return networkRequest;
     }
 
@@ -2289,14 +2227,7 @@
         }
         NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
                 1, NetworkRequest.Type.REQUEST);
-
-        Message message = Message.obtain();
-        message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
-        message.arg1 = 50; // Score
-        message.obj = networkRequest;
-        mNetworkProviderMessenger.send(message);
-        processAllMessages();
-
+        mPhoneSwitcherUT.onRequestNetwork(networkRequest);
         return networkRequest;
     }
 
@@ -2304,10 +2235,6 @@
      * Tell PhoneSwitcher to release a network request.
      */
     private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception {
-        Message message = Message.obtain();
-        message.what = android.net.NetworkProvider.CMD_CANCEL_REQUEST;
-        message.obj = networkRequest;
-        mNetworkProviderMessenger.send(message);
-        processAllMessages();
+        mPhoneSwitcherUT.onReleaseNetwork(networkRequest);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java
new file mode 100644
index 0000000..2fdf9e6
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2024 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.internal.telephony.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.connectivity.android.net.INetworkOfferCallback;
+import android.os.Looper;
+import android.telephony.Annotation.NetCapability;
+import android.telephony.SubscriptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Arrays;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TelephonyNetworkProviderTest extends TelephonyTest {
+
+    private TelephonyNetworkProvider mTelephonyNetworkProvider;
+
+    private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
+    // Mocked classes
+    private DataNetworkController mDataNetworkController2;
+
+
+    /**
+     * Set the preferred data phone, which is supposed to take the network request.
+     *
+     * @param phoneId The phone id
+     */
+    private void setPreferredDataPhone(int phoneId) {
+        doAnswer(invocation -> {
+            TelephonyNetworkRequest request = (TelephonyNetworkRequest)
+                    invocation.getArguments()[0];
+            int id = (int) invocation.getArguments()[1];
+
+            logd("shouldApplyNetworkRequest: request phone id=" + id
+                    + ", preferred data phone id=" + phoneId);
+
+            TelephonyNetworkSpecifier specifier = (TelephonyNetworkSpecifier)
+                    request.getNetworkSpecifier();
+            if (specifier != null) {
+                int subId = specifier.getSubscriptionId();
+                logd("shouldApplyNetworkRequest: requested on sub " + subId);
+                if (subId == 1 && mPhone.getPhoneId() == id) {
+                    logd("shouldApplyNetworkRequest: matched phone 0");
+                    return true;
+                }
+                if (subId == 2 && mPhone2.getPhoneId() == id) {
+                    logd("shouldApplyNetworkRequest: matched phone 1");
+                    return true;
+                }
+                return false;
+            }
+
+            if (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) return true;
+            return id == phoneId;
+        }).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(TelephonyNetworkRequest.class),
+                anyInt());
+    }
+
+    /**
+     * Create a simple network request with internet capability.
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequest() {
+        return createNetworkRequestForSub(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    /**
+     * Create a network request with specified network capabilities.
+     *
+     * @param caps Network capabilities
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequest(@NetCapability int... caps) {
+        return createNetworkRequestForSub(SubscriptionManager.INVALID_SUBSCRIPTION_ID, caps);
+    }
+
+    /**
+     * Create a network request with subscription id specified.
+     *
+     * @param subId The subscription in for the network request
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequestForSub(int subId) {
+        return createNetworkRequestForSub(subId, NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    /**
+     * Create the network request.
+     *
+     * @param subId The subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if no
+     * @param caps Network capabilities in the network request need to specify.
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequestForSub(int subId, @NetCapability int... caps) {
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        Arrays.stream(caps).boxed().toList().forEach(builder::addCapability);
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+            builder.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+            builder.setSubscriptionIds(Set.of(subId));
+        }
+
+        return builder.build();
+    }
+
+    /** Clear all invocations from all DataNetworkControllers. */
+    private void resetInvocations() {
+        clearInvocations(mDataNetworkController);
+        clearInvocations(mDataNetworkController2);
+    }
+
+    /**
+     * Verify the request was sent to the correct phone's DataNetworkController.
+     *
+     * @param phoneId The id of the phone that the request is supposed to send
+     * @param request The network request
+     */
+    private void verifyRequestSentOnPhone(int phoneId, @NonNull NetworkRequest request) {
+        ArgumentCaptor<TelephonyNetworkRequest> requestCaptor =
+                ArgumentCaptor.forClass(TelephonyNetworkRequest.class);
+
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone.getPhoneId() == phoneId) {
+                verify(phone.getDataNetworkController(), times(1)
+                        .description("Did not request on phone " + phoneId))
+                        .addNetworkRequest(requestCaptor.capture());
+                assertThat(requestCaptor.getValue().getNativeNetworkRequest()).isEqualTo(request);
+            } else {
+                verifyNoRequestSentOnPhone(phone.getPhoneId());
+            }
+        }
+    }
+
+    /**
+     * Verify the request was released on the specified phone's DataNetworkController.
+     *
+     * @param phoneId The id of the phone that the request is supposed to send
+     * @param request The network request
+     */
+    private void verifyRequestReleasedOnPhone(int phoneId, @NonNull NetworkRequest request) {
+        ArgumentCaptor<TelephonyNetworkRequest> requestCaptor =
+                ArgumentCaptor.forClass(TelephonyNetworkRequest.class);
+
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone.getPhoneId() == phoneId) {
+                verify(phone.getDataNetworkController(), times(1)
+                        .description("Did not remove on phone " + phoneId))
+                        .removeNetworkRequest(requestCaptor.capture());
+                assertThat(requestCaptor.getValue().getNativeNetworkRequest()).isEqualTo(request);
+            } else {
+                verifyNoRequestReleasedOnPhone(phone.getPhoneId());
+            }
+        }
+    }
+
+    /**
+     * Verify there is no request sent on specified phone.
+     *
+     * @param phoneId The phone id
+     */
+    private void verifyNoRequestSentOnPhone(int phoneId) {
+        verify(PhoneFactory.getPhone(phoneId).getDataNetworkController(), never()
+                .description("Should not request on phone " + phoneId))
+                .addNetworkRequest(any(TelephonyNetworkRequest.class));
+    }
+
+    /**
+     * Verify there is no request released on specified phone.
+     *
+     * @param phoneId The phone id
+     */
+    private void verifyNoRequestReleasedOnPhone(int phoneId) {
+        verify(PhoneFactory.getPhone(phoneId).getDataNetworkController(), never()
+                .description("Should not release on phone " + phoneId))
+                .removeNetworkRequest(any(TelephonyNetworkRequest.class));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        logd("TelephonyNetworkProviderTest +Setup!");
+        super.setUp(getClass().getSimpleName());
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+
+        mDataNetworkController2 = mock(DataNetworkController.class);
+
+        doReturn(0).when(mPhone).getPhoneId();
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(1).when(mPhone2).getPhoneId();
+        doReturn(2).when(mPhone2).getSubId();
+
+        doReturn(mDataNetworkController2).when(mPhone2).getDataNetworkController();
+
+        setPreferredDataPhone(0);
+
+        doAnswer(invocation -> {
+            NetworkProvider provider = (NetworkProvider) invocation.getArguments()[0];
+            provider.setProviderId(1);
+            return 1;
+        }).when(mConnectivityManager).registerNetworkProvider(any(NetworkProvider.class));
+
+        mTelephonyNetworkProvider = new TelephonyNetworkProvider(Looper.myLooper(),
+                mContext, mFeatureFlags);
+
+        ArgumentCaptor<PhoneSwitcherCallback> callbackCaptor =
+                ArgumentCaptor.forClass(PhoneSwitcherCallback.class);
+        verify(mPhoneSwitcher).registerCallback(callbackCaptor.capture());
+        mPhoneSwitcherCallback = callbackCaptor.getValue();
+
+        logd("TelephonyNetworkProviderTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd("tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testRegisterProvider() {
+        verify(mConnectivityManager).registerNetworkProvider(any(TelephonyNetworkProvider.class));
+
+        ArgumentCaptor<NetworkCapabilities> capsCaptor =
+                ArgumentCaptor.forClass(NetworkCapabilities.class);
+        verify(mConnectivityManager).offerNetwork(anyInt(), any(NetworkScore.class),
+                capsCaptor.capture(), any(INetworkOfferCallback.class));
+
+        NetworkCapabilities caps = capsCaptor.getValue();
+
+        TelephonyNetworkRequest.getAllSupportedNetworkCapabilities().forEach(
+                (cap) -> assertThat(caps.hasCapability(cap)));
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)).isTrue();
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)).isTrue();
+        assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
+        assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+
+        assertThat(caps.getNetworkSpecifier()).isInstanceOf(MatchAllNetworkSpecifier.class);
+    }
+
+    @Test
+    public void testRequestNetwork() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+    }
+
+    @Test
+    public void testReleaseNetwork() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+        resetInvocations();
+
+        // Now release the network request.
+        mTelephonyNetworkProvider.onNetworkUnneeded(request);
+        // Should release on phone 0
+        verifyRequestReleasedOnPhone(0, request);
+        resetInvocations();
+
+        // Release the same request again should not result in another remove
+        mTelephonyNetworkProvider.onNetworkUnneeded(request);
+        verifyNoRequestReleasedOnPhone(0);
+        verifyNoRequestReleasedOnPhone(1);
+    }
+
+    @Test
+    public void testRequestNetworkDuplicate() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+
+        resetInvocations();
+        // send the same request again should be blocked.
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        verifyNoRequestSentOnPhone(0);
+        verifyNoRequestSentOnPhone(1);
+    }
+
+    @Test
+    public void testRequestNetworkPreferredPhone1() {
+        setPreferredDataPhone(1);
+
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 1
+        verifyRequestSentOnPhone(1, request);
+    }
+
+    @Test
+    public void testRequestEmergencyNetwork() {
+        setPreferredDataPhone(1);
+
+        NetworkRequest request = createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_EIMS);
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+    }
+
+    @Test
+    public void testRequestNetworkOnSpecifiedSub() {
+        NetworkRequest request = createNetworkRequestForSub(1);
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        verifyRequestSentOnPhone(0, request);
+
+        resetInvocations();
+        request = createNetworkRequestForSub(2);
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 1
+        verifyRequestSentOnPhone(1, request);
+    }
+
+    @Test
+    public void testPreferredDataSwitch() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+        resetInvocations();
+
+        // Now switch from phone 0 to phone 1
+        setPreferredDataPhone(1);
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(1);
+        verifyRequestReleasedOnPhone(0, request);
+        verifyRequestSentOnPhone(1, request);
+        resetInvocations();
+
+        // Now switch back to phone 0
+        setPreferredDataPhone(0);
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(0);
+        verifyRequestReleasedOnPhone(1, request);
+        verifyRequestSentOnPhone(0, request);
+    }
+}