Merge "Implement callingUser propagation in outgoing SMS impl class" into main
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 80d6f61..a993d51 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -34,53 +34,6 @@
   }
 }
 
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "use_alarm_callback"
-  namespace: "telephony"
-  description: "Use alarm callback instead of broadcast."
-  bug: "311476875"
-}
-
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "refine_preferred_data_profile_selection"
-  namespace: "telephony"
-  description: "Upon internet network connect, refine selection of preferred data profile."
-  bug: "311476883"
-}
-
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "unthrottle_check_transport"
-  namespace: "telephony"
-  description: "Check transport when unthrottle."
-  bug: "303922311"
-}
-
-# OWNER=linggm TARGET=24Q1
-flag {
-  name: "relax_ho_teardown"
-  namespace: "telephony"
-  description: "Relax handover tear down if the device is currently in voice call."
-  bug: "270895912"
-}
-
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "allow_mmtel_in_non_vops"
-  namespace: "telephony"
-  description: "Allow bring up MMTEL in nonVops area specified by carrier config."
-  bug: "241198464"
-}
-
-# OWNER=jackyu TARGET=24Q2
-flag {
-  name: "metered_embb_urlcc"
-  namespace: "telephony"
-  description: "Force networks that have PRIORITIZE_BANDWIDTH or PRIORITIZE_LATENCY to be metered."
-  bug: "301310451"
-  }
 
 # OWNER=sarahchin TARGET=24Q3
 flag {
@@ -109,14 +62,6 @@
   bug:"286171724"
 }
 
-# OWNER=nagendranb TARGET=24Q2
-flag {
- name: "notify_data_activity_changed_with_slot"
-  namespace: "telephony"
-  description: "notify data activity changed for slot id"
-  bug: "309896936"
-}
-
 # OWNER=qingqi TARGET=24Q3
 flag {
   name: "vonr_enabled_metric"
@@ -125,14 +70,6 @@
   bug:"288449751"
 }
 
-# OWNER=willycwhu TARGET=24Q2
-flag {
-  name: "ignore_existing_networks_for_internet_allowed_checking"
-  namespace: "telephony"
-  description: "Ignore existing networks when checking if internet is allowed"
-  bug: "284420611"
-}
-
 # OWNER=apsankar TARGET=24Q3
 flag {
   name: "data_call_session_stats_captures_cross_sim_calling"
@@ -141,14 +78,6 @@
   bug: "313956117"
 }
 
-# OWNER=jackyu TARGET=24Q2
-flag {
-  name: "force_iwlan_mms"
-  namespace: "telephony"
-  description: "When QNS prefers MMS on IWLAN, MMS will be attempted on IWLAN if it can, even though if existing cellular network already supports MMS."
-  bug: "316211526"
-}
-
 # OWNER=sewook TARGET=24Q3
 flag {
   name: "reconnect_qualified_network"
@@ -181,4 +110,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/flags/uicc.aconfig b/flags/uicc.aconfig
index cf10c92..f41fad3 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -69,3 +69,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=mewan TARGET=24Q4
+flag {
+    name: "optimization_apdu_sender"
+    namespace: "telephony"
+    description: "This flag controls optimization of apdu sender class."
+    bug:"335257880"
+}
diff --git a/src/java/com/android/internal/telephony/CarrierActionAgent.java b/src/java/com/android/internal/telephony/CarrierActionAgent.java
index 7bb89d0..c4ba77d 100644
--- a/src/java/com/android/internal/telephony/CarrierActionAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierActionAgent.java
@@ -258,6 +258,8 @@
                 return mCarrierActionOnRadioEnabled;
             case CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS:
                 return mCarrierActionReportDefaultNetworkStatus;
+            case EVENT_APN_SETTINGS_CHANGED:
+                return null;  // we don't know if it's enabled, but this is not "unsupported" action
             default:
                 loge("Unsupported action: " + action);
                 return null;
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index a6d24f2..da17f60 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -135,13 +135,9 @@
 
         int subId = sender.getSubId();
 
-        if (mFeatureFlags.notifyDataActivityChangedWithSlot()) {
-            int phoneId = sender.getPhoneId();
-            mTelephonyRegistryMgr.notifyDataActivityChanged(phoneId, subId,
-                    sender.getDataActivityState());
-        } else {
-            mTelephonyRegistryMgr.notifyDataActivityChanged(subId, sender.getDataActivityState());
-        }
+        int phoneId = sender.getPhoneId();
+        mTelephonyRegistryMgr.notifyDataActivityChanged(phoneId, subId,
+                sender.getDataActivityState());
     }
 
     @Override
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/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 8abebe2..de33753 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -4374,9 +4374,9 @@
             riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                     + " params: " + carrierRestrictionRules);
         }
-
         radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setAllowedCarriers", () -> {
-            simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules);
+            simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules,
+                    getHalVersion(HAL_SERVICE_SIM));
         });
     }
 
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 8897db4..a81dbc8 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -1944,6 +1944,31 @@
     }
 
     /**
+     * Convert a list of CarrierIdentifiers into an array of CarrierInfo.aidl
+     *
+     * @param carriers List of CarrierIdentifiers
+     * @return The converted array of CarrierInfos.
+     */
+    public static android.hardware.radio.sim.CarrierInfo[] convertToHalCarrierInfoListAidl(
+            List<CarrierIdentifier> carriers) {
+        android.hardware.radio.sim.CarrierInfo[] result =
+                new android.hardware.radio.sim.CarrierInfo[carriers.size()];
+        for (int i = 0; i < carriers.size(); i++) {
+            CarrierIdentifier ci = carriers.get(i);
+            android.hardware.radio.sim.CarrierInfo carrierInfo =
+                    new android.hardware.radio.sim.CarrierInfo();
+            carrierInfo.mcc = convertNullToEmptyString(ci.getMcc());
+            carrierInfo.mnc = convertNullToEmptyString(ci.getMnc());
+            carrierInfo.spn = ci.getSpn();
+            carrierInfo.imsiPrefix = ci.getImsi();
+            carrierInfo.gid1 = ci.getGid1();
+            carrierInfo.gid2 = ci.getGid2();
+            result[i] = carrierInfo;
+        }
+        return result;
+    }
+
+    /**
      * Convert to Dial defined in radio/1.0/types.hal
      * @param address Address
      * @param clirMode CLIR mode
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/RadioSimProxy.java b/src/java/com/android/internal/telephony/RadioSimProxy.java
index 9bf9a50..1c864fe 100644
--- a/src/java/com/android/internal/telephony/RadioSimProxy.java
+++ b/src/java/com/android/internal/telephony/RadioSimProxy.java
@@ -24,6 +24,8 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
+import java.util.Collections;
+
 /**
  * A holder for IRadioSim.
  * Use getAidl to get IRadioSim and call the AIDL implementations of the HAL APIs.
@@ -512,21 +514,36 @@
 
     /**
      * Call IRadioSim#setAllowedCarriers
-     * @param serial Serial number of request
+     *
+     * @param serial                  Serial number of request
      * @param carrierRestrictionRules Allowed carriers
-     * @throws RemoteException
      */
-    public void setAllowedCarriers(int serial, CarrierRestrictionRules carrierRestrictionRules)
-            throws RemoteException {
+    public void setAllowedCarriers(int serial, CarrierRestrictionRules carrierRestrictionRules,
+            HalVersion halversion) throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
-            // Prepare structure with allowed list, excluded list and priority
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions =
                     new android.hardware.radio.sim.CarrierRestrictions();
-            carrierRestrictions.allowedCarriers = RILUtils.convertToHalCarrierRestrictionListAidl(
-                    carrierRestrictionRules.getAllowedCarriers());
-            carrierRestrictions.excludedCarriers = RILUtils.convertToHalCarrierRestrictionListAidl(
-                    carrierRestrictionRules.getExcludedCarriers());
+            if (halversion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_2)) {
+                carrierRestrictions.allowedCarrierInfoList =
+                        RILUtils.convertToHalCarrierInfoListAidl(
+                                carrierRestrictionRules.getAllowedCarriers());
+                carrierRestrictions.excludedCarrierInfoList =
+                        RILUtils.convertToHalCarrierInfoListAidl(
+                                carrierRestrictionRules.getExcludedCarriers());
+                carrierRestrictions.allowedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(Collections.EMPTY_LIST);
+                carrierRestrictions.excludedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(Collections.EMPTY_LIST);
+            } else {
+                // Prepare structure with allowed list, excluded list and priority
+                carrierRestrictions.allowedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(
+                                carrierRestrictionRules.getAllowedCarriers());
+                carrierRestrictions.excludedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(
+                                carrierRestrictionRules.getExcludedCarriers());
+            }
             carrierRestrictions.allowedCarriersPrioritized =
                     (carrierRestrictionRules.getDefaultCarrierRestriction()
                             == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED);
@@ -534,6 +551,7 @@
             mSimProxy.setAllowedCarriers(serial, carrierRestrictions,
                     RILUtils.convertToHalSimLockMultiSimPolicyAidl(
                             carrierRestrictionRules.getMultiSimPolicy()));
+            Rlog.d(TAG, "RadioSimProxy setAllowedCarriers params = " + carrierRestrictions);
         } else {
             // Prepare structure with allowed list, excluded list and priority
             android.hardware.radio.V1_4.CarrierRestrictionsWithPriority carrierRestrictions =
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index e2f5980..e7500a2 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -1164,6 +1164,9 @@
 
         mDesiredPowerState = power;
         setPowerStateToDesired(forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply);
+        if (mDesiredPowerState) {
+            SatelliteController.getInstance().onSetCellularRadioPowerStateRequested(true);
+        }
     }
 
     /**
@@ -1325,6 +1328,12 @@
                     // Hence, issuing shut down regardless of radio power response
                     mCi.requestShutdown(null);
                 }
+
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception != null) {
+                    loge("EVENT_RADIO_POWER_OFF_DONE: exception=" + ar.exception);
+                    SatelliteController.getInstance().onPowerOffCellularRadioFailed();
+                }
                 break;
 
             // GSM
@@ -4979,7 +4988,7 @@
      */
     public void powerOffRadioSafely() {
         synchronized (this) {
-            SatelliteController.getInstance().onCellularRadioPowerOffRequested();
+            SatelliteController.getInstance().onSetCellularRadioPowerStateRequested(false);
             if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
                 EmergencyStateTracker.getInstance().onCellularRadioPowerOffRequested();
             }
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 59defc3..97692a0 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -112,7 +112,7 @@
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
             int multiSimPolicy) {
         RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
-        boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greater(
+        boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greaterOrEqual(
                 RIL.RADIO_HAL_VERSION_2_2);
         if (rr == null) {
             return;
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index d9ddde0..89b0fec 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -643,10 +643,8 @@
         // Consumer slices are the slices that are allowed to be accessed by regular application to
         // get better performance. They should be metered. This can be turned into configurations in
         // the future.
-        if (mFeatureFlags.meteredEmbbUrlcc()) {
-            meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
-            meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
-        }
+        meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+        meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
 
         return Collections.unmodifiableSet(meteredCapabilities);
     }
@@ -761,7 +759,6 @@
      */
     public boolean allowBringUpNetworkInNonVops(@NetworkRegistrationInfo.RegistrationState
             int regState) {
-        if (!mFeatureFlags.allowMmtelInNonVops()) return false;
         int networkType = -1;
         if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
             networkType = CarrierConfigManager.Ims.NETWORK_TYPE_HOME;
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 2f0c0ff..738c60f 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(),
@@ -1266,21 +1310,19 @@
                         getHandler(), EVENT_VOICE_CALL_ENDED, null);
             }
 
-            if (mFlags.forceIwlanMms()) {
-                if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-                    mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(
-                            getHandler()::post) {
-                        @Override
-                        public void onPreferredTransportChanged(
-                                @NetCapability int networkCapability, boolean forceReconnect) {
-                            if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) {
-                                log("MMS preference changed.");
-                                updateNetworkCapabilities();
-                            }
+            if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+                mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(
+                        getHandler()::post) {
+                    @Override
+                    public void onPreferredTransportChanged(
+                            @NetCapability int networkCapability, boolean forceReconnect) {
+                        if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) {
+                            log("MMS preference changed.");
+                            updateNetworkCapabilities();
                         }
-                    };
-                    mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
-                }
+                    }
+                };
+                mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
             }
 
             // Only add symmetric code here, for example, registering and unregistering.
@@ -1292,7 +1334,7 @@
         @Override
         public void exit() {
             logv("Unregistering all events.");
-            if (mFlags.forceIwlanMms() && mAccessNetworksManagerCallback != null) {
+            if (mAccessNetworksManagerCallback != null) {
                 mAccessNetworksManager.unregisterCallback(mAccessNetworksManagerCallback);
             }
 
@@ -1318,6 +1360,9 @@
             mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
             mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
                     getHandler());
+            if (mFlags.supportNetworkProvider()) {
+                mPhoneSwitcher.unregisterCallback(mPhoneSwitcherCallback);
+            }
             mDataNetworkController.getDataSettingsManager()
                     .unregisterCallback(mDataSettingsManagerCallback);
             mRil.unregisterForPcoData(getHandler());
@@ -1351,13 +1396,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 +1473,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;
@@ -2355,14 +2406,7 @@
                     DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                     // Check if the network is non-VoPS.
                     if (dsri != null && dsri.getVopsSupportInfo() != null
-                            && !dsri.getVopsSupportInfo().isVopsSupported()
-                            // Reflect the actual MMTEL if flag on.
-                            && (mFlags.allowMmtelInNonVops()
-                            // Deceive Connectivity service to satisfy an MMTEL request, this should
-                            // be useless because we reach here if no MMTEL request, then removing
-                            // MMTEL capability shouldn't have any impacts.
-                            || !mDataConfigManager.shouldKeepNetworkUpInNonVops(
-                                    nri.getNetworkRegistrationState()))) {
+                            && !dsri.getVopsSupportInfo().isVopsSupported()) {
                         builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
                     }
                     log("updateNetworkCapabilities: dsri=" + dsri);
@@ -2490,8 +2534,7 @@
         // Check if the feature force MMS on IWLAN is enabled. When the feature is enabled, MMS
         // will be attempted on IWLAN if possible, even if existing cellular networks already
         // supports IWLAN.
-        if (mFlags.forceIwlanMms() && builder.build()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+        if (builder.build().hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
             // If QNS sets MMS preferred on IWLAN, and it is possible to setup an MMS network on
             // IWLAN, then we need to remove the MMS capability on the cellular network. This will
             // allow the new MMS network to be brought up on IWLAN when MMS network request arrives.
@@ -3314,6 +3357,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 +3372,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 +4112,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 4d1786b..89153ab 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -1299,6 +1299,13 @@
      * @param networkRequest The network request.
      */
     private void onAddNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
+        // TelephonyNetworkRequest at TelephonyNetworkProvider layer does not have config assigned
+        // (Because TelephonyNetworkProvider is a singleton across all SIMs. We are not able to
+        // retrieve the right carrier config for it.). So as soon as the request arrives
+        // DataNetworkController, we need to update the config in the request so it can update
+        // some of its config-dependent properties like request priority.
+        networkRequest.updateDataConfig(mDataConfigManager);
+
         // To detect IMS back-to-back release-request anomaly event
         if (mLastImsOperationIsRelease) {
             mLastImsOperationIsRelease = false;
@@ -1501,11 +1508,9 @@
                         .build(), mPhone, mFeatureFlags);
         // If we don't skip checking existing network, then we should check If one of the
         // existing networks can satisfy the internet request, then internet is allowed.
-        if ((!mFeatureFlags.ignoreExistingNetworksForInternetAllowedChecking()
-                || !ignoreExistingNetworks)
-                && mDataNetworkList.stream().anyMatch(
-                        dataNetwork -> internetRequest.canBeSatisfiedBy(
-                                dataNetwork.getNetworkCapabilities()))) {
+        if (!ignoreExistingNetworks && mDataNetworkList.stream().anyMatch(
+                dataNetwork -> internetRequest.canBeSatisfiedBy(
+                        dataNetwork.getNetworkCapabilities()))) {
             return new DataEvaluation(DataEvaluationReason.EXTERNAL_QUERY);
         }
 
@@ -1746,7 +1751,9 @@
                 // Check if request is unmetered (WiFi or unmetered APN).
                 evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
             } else if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                if (!networkRequest.isMeteredRequest()) {
+                boolean isMeteredRequest = mDataConfigManager.isAnyMeteredCapability(
+                        networkRequest.getCapabilities(), mServiceState.getDataRoaming());
+                if (!isMeteredRequest) {
                     evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
                 }
             }
@@ -2507,6 +2514,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.
@@ -2527,7 +2558,7 @@
 
         if (networkRequest.getAttachedNetwork() != null) {
             networkRequest.getAttachedNetwork().detachNetworkRequest(
-                        networkRequest, false /* shouldRetry */);
+                    networkRequest, false /* shouldRetry */);
         }
         log("onRemoveNetworkRequest: Removed " + networkRequest);
     }
@@ -3610,8 +3641,7 @@
             dataNetwork.startHandover(targetTransport, dataHandoverRetryEntry);
         } else if (dataNetwork.shouldDelayImsTearDownDueToInCall()
                 && (dataEvaluation.containsOnly(DataDisallowedReason.NOT_IN_SERVICE)
-                || mFeatureFlags.relaxHoTeardown() && dataEvaluation.isSubsetOf(
-                        DataDisallowedReason.NOT_IN_SERVICE,
+                || dataEvaluation.isSubsetOf(DataDisallowedReason.NOT_IN_SERVICE,
                         DataDisallowedReason.NOT_ALLOWED_BY_POLICY))) {
             // We try our best to preserve the voice call by retrying later
             if (dataHandoverRetryEntry != null) {
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index c50d50a..28fe935 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -432,35 +432,18 @@
      */
     private void onInternetDataNetworkConnected(@NonNull Set<DataNetwork> internetNetworks) {
         DataProfile defaultProfile = null;
-        if (mFeatureFlags.refinePreferredDataProfileSelection()) {
-            // Most of the cases there should be only one.
-            // but in case there are multiple, find the default internet network, and choose the
-            // one which has longest life cycle.
-            defaultProfile = internetNetworks.stream()
-                    .filter(network -> mPreferredDataProfile == null
-                            // Find the one most resembles the current preferred profile,
-                            // avoiding e.g. DUN default network.
-                            || canPreferredDataProfileSatisfy(
-                            network.getAttachedNetworkRequestList()))
-                    .map(DataNetwork::getDataProfile)
-                    .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
-                    .orElse(null);
-        } else {
-            if (internetNetworks.size() == 1) {
-                // Most of the cases there should be only one.
-                defaultProfile = internetNetworks.stream().findFirst().get().getDataProfile();
-            } else if (internetNetworks.size() > 1) {
-                // but in case there are multiple, find the default internet network, and choose the
-                // one which has longest life cycle.
-                defaultProfile = internetNetworks.stream()
-                        .filter(network -> mPreferredDataProfile == null
-                                || canPreferredDataProfileSatisfy(
-                                network.getAttachedNetworkRequestList()))
-                        .map(DataNetwork::getDataProfile)
-                        .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
-                        .orElse(null);
-            }
-        }
+        // Most of the cases there should be only one.
+        // but in case there are multiple, find the default internet network, and choose the
+        // one which has longest life cycle.
+        defaultProfile = internetNetworks.stream()
+                .filter(network -> mPreferredDataProfile == null
+                        // Find the one most resembles the current preferred profile,
+                        // avoiding e.g. DUN default network.
+                        || canPreferredDataProfileSatisfy(
+                        network.getAttachedNetworkRequestList()))
+                .map(DataNetwork::getDataProfile)
+                .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
+                .orElse(null);
 
         // Update a working internet data profile by subid as a future candidate for preferred
         // data profile after APNs are reset to default
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index 2a009c8..318c4b5 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -22,11 +22,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -79,11 +74,6 @@
 public class DataRetryManager extends Handler {
     private static final boolean VDBG = false;
 
-    /** Intent of Alarm Manager for long retry timer. */
-    private static final String ACTION_RETRY = "com.android.internal.telephony.data.ACTION_RETRY";
-    /** The extra key for the hashcode of the retry entry for Alarm Manager. */
-    private static final String ACTION_RETRY_EXTRA_HASHCODE = "extra_retry_hashcode";
-
     /** Event for data setup retry. */
     private static final int EVENT_DATA_SETUP_RETRY = 3;
 
@@ -1078,22 +1068,6 @@
         mRil.registerForOn(this, EVENT_RADIO_ON, null);
         mRil.registerForModemReset(this, EVENT_MODEM_RESET, null);
 
-        if (!mFlags.useAlarmCallback()) {
-            // Register intent of alarm manager for long retry timer
-            IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(ACTION_RETRY);
-            mPhone.getContext().registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (ACTION_RETRY.equals(intent.getAction())) {
-                        DataRetryManager.this.onAlarmIntentRetry(
-                                intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE,
-                                        -1 /*Bad hashcode*/));
-                    }
-                }
-            }, intentFilter);
-        }
-
         if (mDataConfigManager.shouldResetDataThrottlingWhenTacChanges()) {
             mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED,
                     null);
@@ -1531,32 +1505,19 @@
                             ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry),
                     dataRetryEntry.retryDelayMillis);
         } else {
-            if (mFlags.useAlarmCallback()) {
-                // No need to wake up the device, the retry can wait util next time the device wake
-                // up to save power.
-                mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
-                        dataRetryEntry.retryElapsedTime,
-                        "dataRetryHash-" + dataRetryEntry.hashCode() /*debug tag*/,
-                        Runnable::run,
-                        null /*worksource*/,
-                        () -> {
-                            logl("onAlarm retry " + dataRetryEntry);
-                            sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
-                                            ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY,
-                                    dataRetryEntry));
-                        });
-            } else {
-                Intent intent = new Intent(ACTION_RETRY);
-                intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
-                // No need to wake up the device, the retry can wait util next time the device wake
-                // up  to save power.
-                mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
-                        dataRetryEntry.retryElapsedTime,
-                        PendingIntent.getBroadcast(mPhone.getContext(),
-                                dataRetryEntry.hashCode()/*Unique identifier of the retry attempt*/,
-                                intent,
-                                PendingIntent.FLAG_IMMUTABLE));
-            }
+            // No need to wake up the device, the retry can wait util next time the device wake
+            // up to save power.
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+                    dataRetryEntry.retryElapsedTime,
+                    "dataRetryHash-" + dataRetryEntry.hashCode() /*debug tag*/,
+                    Runnable::run,
+                    null /*worksource*/,
+                    () -> {
+                        logl("onAlarm retry " + dataRetryEntry);
+                        sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
+                                        ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY,
+                                dataRetryEntry));
+                    });
         }
     }
 
@@ -1601,8 +1562,7 @@
         // transport.
         mDataThrottlingEntries.removeIf(
                 throttlingEntry -> dataProfile.equals(throttlingEntry.dataProfile)
-                        && (!mFlags.unthrottleCheckTransport()
-                        || throttlingEntry.transport == transport));
+                        && (throttlingEntry.transport == transport));
 
         if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
             // If we don't see the anomaly report after U release, we should remove this check for
@@ -1651,7 +1611,7 @@
             // profile manager.
             Stream<DataThrottlingEntry> stream = mDataThrottlingEntries.stream();
             stream = stream.filter(entry -> entry.expirationTimeMillis > now
-                    && (!mFlags.unthrottleCheckTransport() || entry.transport == transport));
+                    && entry.transport == transport);
             if (dataProfile.getApnSetting() != null) {
                 stream = stream
                         .filter(entry -> entry.dataProfile.getApnSetting() != null)
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index fb112d9..0b9ac27 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -326,7 +326,7 @@
                     @Override
                     public void onUserDataEnabledChanged(boolean enabled,
                             @NonNull String callingPackage) {
-                        log("phone" + phone.getPhoneId() + " onUserDataEnabledChanged "
+                        log("phone " + phone.getPhoneId() + " onUserDataEnabledChanged "
                                 + enabled + " by " + callingPackage
                                 + ", reevaluating mobile data policies");
                         DataSettingsManager.this.updateDataEnabledAndNotify(
@@ -335,6 +335,16 @@
                 });
             }
         }
+        SubscriptionManagerService.getInstance().registerCallback(
+                new SubscriptionManagerService.SubscriptionManagerServiceCallback(this::post) {
+                    @Override
+                    public void onDefaultDataSubscriptionChanged(int subId) {
+                        log((subId == mSubId ? "Became" : "Not")
+                                + " default data sub, reevaluating mobile data policies");
+                        DataSettingsManager.this.updateDataEnabledAndNotify(
+                                TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+                    }
+                });
         updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_UNKNOWN);
     }
 
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 67639eb..ca1ca21 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -137,10 +137,6 @@
                 CAPABILITY_ATTRIBUTE_APN_SETTING | CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_DNN)
     );
 
-    /** The phone instance. */
-    @NonNull
-    private final Phone mPhone;
-
     /**
      * Native network request from the clients. See {@link NetworkRequest};
      */
@@ -164,8 +160,8 @@
     /**
      * Data config manager for retrieving data config.
      */
-    @NonNull
-    private final DataConfigManager mDataConfigManager;
+    @Nullable
+    private DataConfigManager mDataConfigManager;
 
     /**
      * The attached data network. Note that the data network could be in any state. {@code null}
@@ -205,7 +201,19 @@
      */
     public TelephonyNetworkRequest(@NonNull NetworkRequest request, @NonNull Phone phone,
                                    @NonNull FeatureFlags featureFlags) {
-        mPhone = phone;
+        this(request, featureFlags);
+        mDataConfigManager = phone.getDataNetworkController().getDataConfigManager();
+        updatePriority();
+    }
+
+    /**
+     * Constructor
+     *
+     * @param request The native network request from the clients.
+     * @param featureFlags The feature flag
+     */
+    public TelephonyNetworkRequest(@NonNull NetworkRequest request,
+                                   @NonNull FeatureFlags featureFlags) {
         mNativeNetworkRequest = request;
         mFeatureFlags = featureFlags;
 
@@ -222,7 +230,15 @@
         // to satisfy it.
         mState = REQUEST_STATE_UNSATISFIED;
         mCreatedTimeMillis = SystemClock.elapsedRealtime();
-        mDataConfigManager = phone.getDataNetworkController().getDataConfigManager();
+    }
+
+    /**
+     * Update the associated data config manager.
+     *
+     * @param dataConfigManager Data config manager
+     */
+    public void updateDataConfig(@NonNull DataConfigManager dataConfigManager) {
+        mDataConfigManager = dataConfigManager;
         updatePriority();
     }
 
@@ -315,13 +331,15 @@
                 if (mNativeNetworkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
                         && !mNativeNetworkRequest.hasTransport(
                                 NetworkCapabilities.TRANSPORT_SATELLITE)) {
-                    if (Arrays.stream(getCapabilities()).noneMatch(mDataConfigManager
-                            .getForcedCellularTransportCapabilities()::contains)) {
-                        // If the request is explicitly for the cellular, then the data profile
-                        // needs to support cellular.
-                        if (!dataProfile.getApnSetting().isForInfrastructure(
-                                ApnSetting.INFRASTRUCTURE_CELLULAR)) {
-                            return false;
+                    if (mDataConfigManager != null) {
+                        if (Arrays.stream(getCapabilities()).noneMatch(mDataConfigManager
+                                .getForcedCellularTransportCapabilities()::contains)) {
+                            // If the request is explicitly for the cellular, then the data profile
+                            // needs to support cellular.
+                            if (!dataProfile.getApnSetting().isForInfrastructure(
+                                    ApnSetting.INFRASTRUCTURE_CELLULAR)) {
+                                return false;
+                            }
                         }
                     }
                 } else if (mNativeNetworkRequest.hasTransport(
@@ -371,10 +389,12 @@
      * Update the priority from data config manager.
      */
     public void updatePriority() {
-        mPriority = Arrays.stream(mNativeNetworkRequest.getCapabilities())
-                .map(mDataConfigManager::getNetworkCapabilityPriority)
-                .max()
-                .orElse(0);
+        if (mDataConfigManager != null) {
+            mPriority = Arrays.stream(mNativeNetworkRequest.getCapabilities())
+                    .map(mDataConfigManager::getNetworkCapabilityPriority)
+                    .max()
+                    .orElse(0);
+        }
     }
 
     /**
@@ -387,6 +407,7 @@
     @NetCapability
     public int getHighestPriorityApnTypeNetworkCapability() {
         if (!hasAttribute(CAPABILITY_ATTRIBUTE_APN_SETTING)) return -1;
+        if (mDataConfigManager == null) return -1;
         return Arrays.stream(getCapabilities()).boxed()
                 .filter(cap -> DataUtils.networkCapabilityToApnType(cap) != ApnSetting.TYPE_NONE)
                 .max(Comparator.comparingInt(mDataConfigManager::getNetworkCapabilityPriority))
@@ -403,6 +424,7 @@
      */
     @NetCapability
     public int getHighestPrioritySupportedNetworkCapability() {
+        if (mDataConfigManager == null) return -1;
         return Arrays.stream(getCapabilities()).boxed()
                 .filter(CAPABILITY_ATTRIBUTE_MAP::containsKey)
                 .max(Comparator.comparingInt(mDataConfigManager::getNetworkCapabilityPriority))
@@ -466,7 +488,7 @@
      *
      * @param evaluation The data evaluation result.
      */
-    public void setEvaluation(@NonNull DataEvaluation evaluation) {
+    public void setEvaluation(@Nullable DataEvaluation evaluation) {
         mEvaluation = evaluation;
     }
 
@@ -487,14 +509,6 @@
     }
 
     /**
-     * @return {@code true} if this network request can result in bringing up a metered network.
-     */
-    public boolean isMeteredRequest() {
-        return mDataConfigManager.isAnyMeteredCapability(
-                getCapabilities(), mPhone.getServiceState().getDataRoaming());
-    }
-
-    /**
      * Get Os/App id from the network request.
      *
      * @return Os/App id. {@code null} if the request does not have traffic descriptor based network
@@ -547,7 +561,7 @@
         return "[" + mNativeNetworkRequest + ", mPriority=" + mPriority
                 + ", state=" + requestStateToString(mState)
                 + ", mAttachedDataNetwork=" + (mAttachedDataNetwork != null
-                ? mAttachedDataNetwork.name() : null) + ", isMetered=" + isMeteredRequest()
+                ? mAttachedDataNetwork.name() : null)
                 + ", created time=" + DataUtils.elapsedTimeToString(mCreatedTimeMillis)
                 + ", evaluation result=" + mEvaluation + "]";
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 194865b..0a39865 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -116,6 +116,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteSubscriberInfo;
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -241,6 +242,7 @@
     private static final int EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT = 46;
     private static final int EVENT_WIFI_CONNECTIVITY_STATE_CHANGED = 47;
     private static final int EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT = 48;
+    protected static final int EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT = 49;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -344,9 +346,13 @@
     private final Object mIsSatelliteEnabledLock = new Object();
     @GuardedBy("mIsSatelliteEnabledLock")
     private Boolean mIsSatelliteEnabled = null;
-    private final Object mIsRadioOnLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected final Object mIsRadioOnLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mIsRadioOn;
     @GuardedBy("mIsRadioOnLock")
-    private boolean mIsRadioOn = false;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mRadioOffRequested = false;
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected final Object mSatelliteViaOemProvisionLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -1325,6 +1331,14 @@
                         mIsRadioOn = true;
                     } else if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
                         resetCarrierRoamingSatelliteModeParams();
+                        synchronized (mIsRadioOnLock) {
+                            if (mRadioOffRequested) {
+                                logd("EVENT_RADIO_STATE_CHANGED: set mIsRadioOn to false");
+                                stopWaitForCellularModemOffTimer();
+                                mIsRadioOn = false;
+                                mRadioOffRequested = false;
+                            }
+                        }
                     }
                 }
 
@@ -1635,6 +1649,13 @@
                 break;
             }
 
+            case EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT: {
+                plogw("Timed out to wait for cellular modem OFF state");
+                synchronized (mIsRadioOnLock) {
+                    mRadioOffRequested = false;
+                }
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1716,6 +1737,12 @@
                             SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
                     return;
                 }
+                if (mRadioOffRequested) {
+                    ploge("Radio is being powering off, can not enable satellite");
+                    sendErrorAndReportSessionMetrics(
+                            SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
+                    return;
+                }
             }
 
             if (mTelecomManager.isInEmergencyCall()) {
@@ -2900,27 +2927,50 @@
 
     /**
      * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
-     * {@link SatelliteController} that it has received a request to power off the cellular radio
-     * modem. {@link SatelliteController} will then power off the satellite modem.
+     * {@link SatelliteController} that it has received a request to power on or off the cellular
+     * radio modem.
+     *
+     * @param powerOn {@code true} means cellular radio is about to be powered on, {@code false}
+     *                 means cellular modem is about to be powered off.
      */
-    public void onCellularRadioPowerOffRequested() {
-        logd("onCellularRadioPowerOffRequested()");
+    public void onSetCellularRadioPowerStateRequested(boolean powerOn) {
+        logd("onSetCellularRadioPowerStateRequested: powerOn=" + powerOn);
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            plogd("onCellularRadioPowerOffRequested: oemEnabledSatelliteFlag is disabled");
+            plogd("onSetCellularRadioPowerStateRequested: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
         synchronized (mIsRadioOnLock) {
-            mIsRadioOn = false;
+            mRadioOffRequested = !powerOn;
         }
-        requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
-                new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        plogd("onRadioPowerOffRequested: requestSatelliteEnabled result=" + result);
-                    }
-                });
+        if (powerOn) {
+            stopWaitForCellularModemOffTimer();
+        } else {
+            requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    false /* enableSatellite */, false /* enableDemoMode */,
+                    false /* isEmergency */,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("onSetCellularRadioPowerStateRequested: requestSatelliteEnabled"
+                                    + " result=" + result);
+                        }
+                    });
+            startWaitForCellularModemOffTimer();
+        }
+    }
+
+    /**
+     * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
+     * {@link SatelliteController} that the request to power off the cellular radio modem has
+     * failed.
+     */
+    public void onPowerOffCellularRadioFailed() {
+        logd("onPowerOffCellularRadioFailed");
+        synchronized (mIsRadioOnLock) {
+            mRadioOffRequested = false;
+            stopWaitForCellularModemOffTimer();
+        }
     }
 
     /**
@@ -4956,6 +5006,32 @@
                 R.integer.config_wait_for_satellite_enabling_response_timeout_millis);
     }
 
+    private long getWaitForCellularModemOffTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_satellite_wait_for_cellular_modem_off_timeout_millis);
+    }
+
+    private void startWaitForCellularModemOffTimer() {
+        synchronized (mIsRadioOnLock) {
+            if (hasMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT)) {
+                plogd("startWaitForCellularModemOffTimer: the timer was already started");
+                return;
+            }
+            long timeoutMillis = getWaitForCellularModemOffTimeoutMillis();
+            plogd("Start timer to wait for cellular modem OFF state, timeoutMillis="
+                    + timeoutMillis);
+            sendMessageDelayed(obtainMessage(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT),
+                    timeoutMillis);
+        }
+    }
+
+    private void stopWaitForCellularModemOffTimer() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            plogd("Stop timer to wait for cellular modem OFF state");
+            removeMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT);
+        }
+    }
+
     private void startWaitForSatelliteEnablingResponseTimer(
             @NonNull RequestSatelliteEnabledArgument argument) {
         synchronized (mSatelliteEnabledRequestLock) {
@@ -5524,14 +5600,20 @@
 
     // The subscriberId for ntnOnly SIMs is the Iccid, whereas for ESOS supported SIMs, the
     // subscriberId is the Imsi prefix 6 digit + phone number.
-    private @NonNull String getSubscriberId(SubscriptionInfo info) {
+    private Pair<String, Integer> getSubscriberIdAndType(SubscriptionInfo info) {
+        String subscriberId = "";
+        @SatelliteSubscriberInfo.SubscriberIdType int subscriberIdType =
+                SatelliteSubscriberInfo.ICCID;
         if (info.isSatelliteESOSSupported()) {
-            return getPhoneNumberBasedCarrier(info.getSubscriptionId());
+            subscriberId = getPhoneNumberBasedCarrier(info.getSubscriptionId());
+            subscriberIdType = SatelliteSubscriberInfo.IMSI_MSISDN;
         }
         if (info.isOnlyNonTerrestrialNetwork()) {
-            return info.getIccId();
+            subscriberId = info.getIccId();
         }
-        return "";
+        logd("getSubscriberIdAndType: subscriberId=" + subscriberId + ", subscriberIdType="
+                + subscriberIdType);
+        return new Pair<>(subscriberId, subscriberIdType);
     }
 
     private String getPhoneNumberBasedCarrier(int subId) {
@@ -5540,7 +5622,6 @@
         SubscriptionManager subscriptionManager = mContext.getSystemService(
                 SubscriptionManager.class);
         String phoneNumber = subscriptionManager.getPhoneNumber(subId);
-
         if (phoneNumber == null) {
             logd("getPhoneNumberBasedCarrier: phoneNumber null");
             return "";
@@ -5603,77 +5684,59 @@
      * @param result The result receiver, which returns the list of prioritized satellite tokens
      * to be used for provision if the request is successful or an error code if the request failed.
      */
-    public void requestProvisionSubscriberIds(@NonNull ResultReceiver result) {
+    public void requestSatelliteSubscriberProvisionStatus(@NonNull ResultReceiver result) {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
             result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
             return;
         }
 
-        List<SatelliteSubscriberInfo> list = new ArrayList<>();
+        List<SatelliteSubscriberProvisionStatus> list = new ArrayList<>();
         synchronized (mSatelliteTokenProvisionedLock) {
             mSubscriberIdPerSub = new HashMap<>();
             for (int priority : mSubsInfoListPerPriority.keySet()) {
                 List<SubscriptionInfo> infoList = mSubsInfoListPerPriority.get(priority);
                 if (infoList == null) {
-                    logd("requestProvisionSubscriberIds: no exist this priority " + priority);
+                    logd("requestSatelliteSubscriberProvisionStatus: no exist this priority "
+                            + priority);
                     continue;
                 }
                 for (SubscriptionInfo info : infoList) {
-                    String subscriberId = getSubscriberId(info);
+                    Pair<String, Integer> subscriberIdPair = getSubscriberIdAndType(info);
+                    String subscriberId = subscriberIdPair.first;
                     int carrierId = info.getCarrierId();
                     String apn = getConfigForSubId(info.getSubscriptionId())
                             .getString(KEY_SATELLITE_NIDD_APN_NAME_STRING);
-                    logd("requestProvisionSubscriberIds: subscriberId:" + subscriberId
-                            + " , carrierId=" + carrierId + " , apn=" + apn);
+                    logd("requestSatelliteSubscriberProvisionStatus: subscriberId:"
+                            + subscriberId + " , carrierId=" + carrierId + " , apn=" + apn);
                     if (subscriberId.isEmpty()) {
-                        logd("requestProvisionSubscriberIds: getSubscriberId failed skip this "
-                                + "subscriberId.");
+                        logd("requestSatelliteSubscriberProvisionStatus: getSubscriberId "
+                                + "failed skip this subscriberId.");
                         continue;
                     }
-                    list.add(new SatelliteSubscriberInfo(subscriberId, carrierId, apn));
+                    SatelliteSubscriberInfo satelliteSubscriberInfo =
+                            new SatelliteSubscriberInfo.Builder().setSubscriberId(subscriberId)
+                                    .setCarrierId(carrierId).setNiddApn(apn)
+                                    .setSubId(info.getSubscriptionId())
+                                    .setSubscriberIdType(subscriberIdPair.second)
+                                    .build();
+                    boolean provisioned = mProvisionedSubscriberId.getOrDefault(
+                            subscriberId, false);
+                    logd("requestSatelliteSubscriberProvisionStatus: satelliteSubscriberInfo="
+                            + satelliteSubscriberInfo + ", provisioned=" + provisioned);
+                    list.add(new SatelliteSubscriberProvisionStatus.Builder()
+                            .setSatelliteSubscriberInfo(satelliteSubscriberInfo)
+                            .setProvisionStatus(provisioned).build());
                     mSubscriberIdPerSub.put(subscriberId, info.getSubscriptionId());
                 }
             }
         }
 
-        logd("requestProvisionSubscriberIds: " + list);
+        logd("requestSatelliteSubscriberProvisionStatus: " + list);
         final Bundle bundle = new Bundle();
         bundle.putParcelableList(SatelliteManager.KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN, list);
         result.send(SATELLITE_RESULT_SUCCESS, bundle);
     }
 
-    /**
-     * Request to get provisioned status for given a satellite subscriber id.
-     *
-     * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
-     * @param result The result receiver, which returns the provisioned status of the token if the
-     * request is successful or an error code if the request failed.
-     */
-    public void requestIsProvisioned(@NonNull String satelliteSubscriberId,
-            @NonNull ResultReceiver result) {
-        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
-            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
-            return;
-        }
-        if (satelliteSubscriberId.isEmpty()) {
-            result.send(SATELLITE_RESULT_INVALID_ARGUMENTS, null);
-            return;
-        }
-
-        boolean isProvisioned = false;
-        synchronized (mSatelliteTokenProvisionedLock) {
-            if (mProvisionedSubscriberId.getOrDefault(satelliteSubscriberId, false)) {
-                isProvisioned = true;
-            }
-        }
-
-        logd("requestIsProvisioned: satelliteSubscriberId=" + satelliteSubscriberId
-                + " , isProvisioned=" + isProvisioned);
-        final Bundle bundle = new Bundle();
-        bundle.putBoolean(SatelliteManager.KEY_IS_SATELLITE_PROVISIONED, isProvisioned);
-        result.send(SATELLITE_RESULT_SUCCESS, bundle);
-    }
-
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected boolean isSubscriptionProvisioned(int subId) {
         plogd("isSubscriptionProvisioned: subId=" + subId);
@@ -5682,8 +5745,8 @@
             return false;
         }
 
-        String subscriberId = getSubscriberId(
-                mSubscriptionManagerService.getSubscriptionInfo(subId));
+        String subscriberId = getSubscriberIdAndType(
+                mSubscriptionManagerService.getSubscriptionInfo(subId)).first;
         if (subscriberId.isEmpty()) {
             plogd("isSubscriptionProvisioned: subId=" + subId + " subscriberId is empty.");
             return false;
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index 9988ea0..f24d484 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony.satellite;
 
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT;
 import static android.telephony.ServiceState.STATE_EMERGENCY_ONLY;
 import static android.telephony.ServiceState.STATE_IN_SERVICE;
 import static android.telephony.ServiceState.STATE_OUT_OF_SERVICE;
@@ -43,7 +42,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.OutcomeReceiver;
-import android.os.PersistableBundle;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.telecom.Connection;
@@ -58,6 +56,7 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -73,6 +72,7 @@
 import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.SatelliteStats;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
@@ -160,6 +160,13 @@
                 plogd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
                 sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned));
             }
+
+            @Override
+            public void onSatelliteSubscriptionProvisionStateChanged(
+                    List<SatelliteSubscriberProvisionStatus> satelliteSubscriberProvisionStatus) {
+                plogd("onSatelliteSubscriptionProvisionStateChanged: "
+                        + satelliteSubscriberProvisionStatus);
+            }
         };
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 749f190..8dad3ec 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfo;
@@ -101,23 +100,7 @@
     }
 
     @Test @SmallTest
-    public void testNotifyDataActivity() throws Exception {
-        when(mFeatureFlags.notifyDataActivityChangedWithSlot()).thenReturn(false);
-        //mock data activity state
-        doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
-        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
-        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(0),
-                eq(TelephonyManager.DATA_ACTIVITY_NONE));
-
-        doReturn(1).when(mPhone).getSubId();
-        doReturn(TelephonyManager.DATA_ACTIVITY_IN).when(mPhone).getDataActivityState();
-        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
-        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(1),
-                eq(TelephonyManager.DATA_ACTIVITY_IN));
-    }
-    @Test @SmallTest
     public void testNotifyDataActivityWithSlot() throws Exception {
-        when(mFeatureFlags.notifyDataActivityChangedWithSlot()).thenReturn(true);
         //mock data activity state
         doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
         doReturn(PHONE_ID).when(mPhone).getPhoneId();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index b9fac4a..1465176 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
@@ -594,7 +595,7 @@
         sst.setRadioPowerForReason(false, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
-        verify(mSatelliteController).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(false));
         clearInvocations(mSatelliteController);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
@@ -602,7 +603,7 @@
                 TelephonyManager.RADIO_POWER_REASON_USER);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
-        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController, never()).onSetCellularRadioPowerStateRequested(anyBoolean());
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
 
@@ -610,7 +611,7 @@
         // had been turned off for.
         sst.setRadioPowerForReason(true, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
-        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(true));
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
 
@@ -1928,6 +1929,8 @@
         sst.setRadioPower(false);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(false));
+        verify(mSatelliteController).onPowerOffCellularRadioFailed();
         sst.requestShutdown();
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
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/DataConfigManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
index 005b312..95cefd8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
@@ -146,7 +146,6 @@
 
     @Test
     public void testMeteredNetworkCapabilities() {
-        doReturn(true).when(mFeatureFlags).meteredEmbbUrlcc();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[] {ApnSetting.TYPE_MMS_STRING, ApnSetting.TYPE_DEFAULT_STRING});
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
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..5dce5c2 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();
@@ -893,8 +890,6 @@
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
         doReturn(true).when(mFeatureFlags).satelliteInternet();
-        doReturn(true).when(mFeatureFlags)
-                .ignoreExistingNetworksForInternetAllowedChecking();
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
         doReturn(true).when(mMockPackageManager).hasSystemFeature(anyString());
 
@@ -2844,7 +2839,6 @@
 
     @Test
     public void testHandoverDataNetworkNotAllowedByPolicyDelayDueToVoiceCall() throws Exception {
-        doReturn(true).when(mFeatureFlags).relaxHoTeardown();
         // Config delay IMS tear down enabled
         mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL,
                 true);
@@ -3901,7 +3895,6 @@
 
     @Test
     public void testNonVoPStoVoPSImsSetup() throws Exception {
-        doReturn(true).when(mFeatureFlags).allowMmtelInNonVops();
         mDataNetworkControllerUT.getDataSettingsManager().setDataRoamingEnabled(true);
         // Config that allows non-vops bring up when Roaming
         mCarrierConfig.putIntArray(CarrierConfigManager.Ims
@@ -4908,14 +4901,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 +4925,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 +4938,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 +5464,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..f832a6b 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;
@@ -2418,7 +2419,6 @@
 
     @Test
     public void testMmsCapabilityRemovedWhenMmsPreferredOnIwlan() throws Exception {
-        doReturn(true).when(mFeatureFlags).forceIwlanMms();
         setupDataNetwork();
 
         TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
@@ -2662,4 +2662,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/DataProfileManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
index 30ce46f..96d0c8c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -1046,7 +1046,6 @@
 
     @Test
     public void testSetPreferredDataProfile() {
-        doReturn(true).when(mFeatureFlags).refinePreferredDataProfileSelection();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
index 89cdc47..78bb553 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -341,7 +341,6 @@
 
     @Test
     public void testDataSetupUnthrottling() throws Exception {
-        doReturn(true).when(mFeatureFlags).unthrottleCheckTransport();
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
@@ -807,7 +806,6 @@
 
     @Test
     public void testDataRetryLongTimer() {
-        doReturn(true).when(mFeatureFlags).useAlarmCallback();
         // Rule requires a long timer
         DataSetupRetryRule retryRule = new DataSetupRetryRule(
                 "capabilities=internet, retry_interval=120000, maximum_retries=2");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
index 3f18a3a..620cf39 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -184,6 +185,49 @@
     }
 
     @Test
+    public void testUpdateDataEnabledAndNotifyOverrideDdsChange() throws Exception {
+        // Mock 2nd phone the DDS phone.
+        int phone2Id = 1;
+        int phone2SubId = 2;
+        doReturn(phone2SubId).when(mSubscriptionManagerService).getDefaultDataSubId();
+        Phone phone2 = Mockito.mock(Phone.class);
+        doReturn(phone2Id).when(phone2).getPhoneId();
+        doReturn(phone2SubId).when(phone2).getSubId();
+        doReturn(phone2Id).when(mSubscriptionManagerService).getPhoneId(phone2SubId);
+        DataSettingsManager dataSettingsManager2 = Mockito.mock(DataSettingsManager.class);
+        doReturn(dataSettingsManager2).when(phone2).getDataSettingsManager();
+        doReturn(true).when(phone2).isUserDataEnabled();
+
+        mPhones = new Phone[] {mPhone, phone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        ArgumentCaptor<SubscriptionManagerService.SubscriptionManagerServiceCallback>
+                callbackArgumentCaptor = ArgumentCaptor
+                .forClass(SubscriptionManagerService.SubscriptionManagerServiceCallback.class);
+
+        mDataSettingsManagerUT.sendEmptyMessage(11 /* EVENT_INITIALIZE */);
+        mDataSettingsManagerUT.setDataEnabled(TelephonyManager.DATA_ENABLED_REASON_USER, false, "");
+        processAllMessages();
+
+        // Verify listening to DDS change callback
+        verify(mSubscriptionManagerService, times(2))
+                .registerCallback(callbackArgumentCaptor.capture());
+        SubscriptionManagerService.SubscriptionManagerServiceCallback callback =
+                callbackArgumentCaptor.getValue();
+
+        // Mock the phone as nonDDS auto switch override enabled.
+        clearInvocations(mPhones);
+        mDataSettingsManagerUT.setMobileDataPolicy(
+                TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true);
+        processAllMessages();
+        verify(mPhone).notifyDataEnabled(true, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+
+        // The phone became DDS, data should be disabled
+        doReturn(mPhone.getSubId()).when(mSubscriptionManagerService).getDefaultDataSubId();
+        callback.onDefaultDataSubscriptionChanged(mPhone.getSubId());
+        verify(mPhone).notifyDataEnabled(false, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+    }
+
+    @Test
     public void testNotifyDataEnabledFromNewValidSubId() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mDataSettingsManagerUT.registerCallback(
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);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index 9493dc7..a3a189b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -125,6 +125,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
@@ -183,6 +184,8 @@
     private static final int[] ACTIVE_SUB_IDS = {SUB_ID};
     private static final int TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS =
             (int) TimeUnit.SECONDS.toMillis(60);
+    private static final int TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(60);
 
     private static final String SATELLITE_PLMN = "00103";
     private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
@@ -517,6 +520,9 @@
         mContextFixture.putIntResource(
                 R.integer.config_wait_for_satellite_enabling_response_timeout_millis,
                 TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        mContextFixture.putIntResource(
+                R.integer.config_satellite_wait_for_cellular_modem_off_timeout_millis,
+                TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS);
         doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
 
         mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle();
@@ -727,6 +733,87 @@
         processAllMessages();
         verify(mMockSatelliteModemInterface, times(5))
                 .requestIsSatelliteSupported(any(Message.class));
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Radio is off during TN -> NTN image switch, SatelliteController should not set radio
+        // state to OFF
+        setRadioPower(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Turn on radio
+        setRadioPower(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // SatelliteController should set the radio state to OFF
+        setRadioPower(false);
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Turn on radio
+        setRadioPower(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Modem fails to power off radio. APM is disabled
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // The timer WaitForCellularModemOff time out
+        moveTimeForward(TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Modem failed to power off the radio
+        mSatelliteControllerUT.onPowerOffCellularRadioFailed();
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
     }
 
     @Test
@@ -847,7 +934,7 @@
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
         setRadioPower(false);
-        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
         processAllMessages();
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
         processAllMessages();
@@ -1061,12 +1148,49 @@
 
         resetSatelliteControllerUTToOnAndProvisionedState();
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
-        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
         processAllMessages();
         // Satellite should not be powered off since the feature flag oemEnabledSatelliteFlag is
         // disabled
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully disable satellite.
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Fail to enable satellite when radio is being powered off.
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        // Radio is being powered off, can not enable satellite
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        // Modem failed to power off
+        mSatelliteControllerUT.onPowerOffCellularRadioFailed();
+
+        // Successfully enable satellite when radio is on.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
@@ -1447,6 +1571,12 @@
                                     + "semaphore, ex=" + ex);
                         }
                     }
+
+                    @Override
+                    public void onSatelliteSubscriptionProvisionStateChanged(
+                            List<SatelliteSubscriberProvisionStatus> status) {
+                        logd("onSatelliteSubscriptionProvisionStateChanged: " + status);
+                    }
                 };
         int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
                 SUB_ID, callback);
@@ -4712,5 +4842,21 @@
                 mIsSatelliteViaOemProvisioned = isProvisioned;
             }
         }
+
+        public boolean isRadioOn() {
+            synchronized (mIsRadioOnLock) {
+                return mIsRadioOn;
+            }
+        }
+
+        public boolean isRadioOffRequested() {
+            synchronized (mIsRadioOnLock) {
+                return mRadioOffRequested;
+            }
+        }
+
+        public boolean isWaitForCellularModemOffTimerStarted() {
+            return hasMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT);
+        }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
index 27f743f..846d9b6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc.euicc.apdu;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -30,7 +31,6 @@
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 
-import org.mockito.ArgumentCaptor;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -48,13 +48,12 @@
         int[] responseInts = isException ? null : getSelectResponse(responseObject.toString());
         Throwable exception = isException ? (Throwable) responseObject : null;
 
-        ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
         doAnswer((Answer<Void>) invocation -> {
-            Message msg = response.getValue();
+            Message msg = invocation.getArgument(2);
             AsyncResult.forMessage(msg, responseInts, exception);
             msg.sendToTarget();
             return null;
-        }).when(mockCi).iccOpenLogicalChannel(anyString(), anyInt(), response.capture());
+        }).when(mockCi).iccOpenLogicalChannel(anyString(), anyInt(), any());
         return LOGICAL_CHANNEL;
     }
 
@@ -64,22 +63,20 @@
      */
     public static void mockSendToLogicalChannel(CommandsInterface mockCi, int channel,
             Object... responseObjects) {
-        ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
-
         doAnswer(new Answer() {
             private int mIndex = 0;
 
             @Override
             public Object answer(InvocationOnMock invocation) throws Throwable {
-                Object responseObject = responseObjects[mIndex++];
-                mockIccTransmitApduLogicalChannelResponse(response, responseObject);
+                Object response = responseObjects[mIndex++];
+                mockIccTransmitApduLogicalChannelResponse(invocation.getArgument(8), response);
                 return null;
             }
         }).when(mockCi).iccTransmitApduLogicalChannel(eq(channel), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), anyBoolean(), response.capture());
+                anyInt(), anyInt(), anyString(), anyBoolean(), any());
     }
 
-    private static void mockIccTransmitApduLogicalChannelResponse(ArgumentCaptor<Message> response,
+    private static void mockIccTransmitApduLogicalChannelResponse(Message msg,
             Object responseObject) throws Throwable {
 
         boolean isException = responseObject instanceof Throwable;
@@ -95,20 +92,18 @@
         IccIoResult result = isException ? null : new IccIoResult(sw1, sw2, hex);
         Throwable exception = isException ? (Throwable) responseObject : null;
 
-        Message msg = response.getValue();
         AsyncResult.forMessage(msg, result, exception);
         msg.sendToTarget();
     }
 
     public static void mockCloseLogicalChannel(CommandsInterface mockCi, int channel) {
-        ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
         doAnswer((Answer<Void>) invocation -> {
-            Message msg = response.getValue();
+            Message msg = invocation.getArgument(2);
             AsyncResult.forMessage(msg);
             msg.sendToTarget();
             return null;
         }).when(mockCi).iccCloseLogicalChannel(eq(channel),
-                eq(true /*isEs10*/), response.capture());
+                eq(true /*isEs10*/), any());
     }
 
     private static int[] getSelectResponse(String responseHex) {