Merge "Configure satellite network capabilities" into main
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 4177cc4..20761e2 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -714,8 +714,7 @@
     /**
      * Update the voice over PS related config from the carrier config.
      */
-    private void updateVopsConfig() {
-        synchronized (this) {
+    private synchronized void updateVopsConfig() {
             mShouldKeepNetworkUpInNonVops = mCarrierConfig.getBoolean(CarrierConfigManager
                     .Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL);
             int[] allowedNetworkTypes = mCarrierConfig.getIntArray(
@@ -723,7 +722,6 @@
             if (allowedNetworkTypes != null) {
                 Arrays.stream(allowedNetworkTypes).forEach(mEnabledVopsNetworkTypesInNonVops::add);
             }
-        }
     }
 
     /**
@@ -886,6 +884,14 @@
     }
 
     /**
+     * @return What kind of traffic is supported on an unrestricted satellite network.
+     */
+    @CarrierConfigManager.SATELLITE_DATA_SUPPORT_MODE
+    public int getSatelliteDataSupportMode() {
+        return mCarrierConfig.getInt(CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT);
+    }
+
+    /**
      * @return Whether data throttling should be reset when the TAC changes from the carrier config.
      */
     public boolean shouldResetDataThrottlingWhenTacChanges() {
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 57d242b..8369874 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -2445,6 +2445,7 @@
         }
 
         // Always start with not-restricted, and then remove if needed.
+        // By default, NET_CAPABILITY_NOT_RESTRICTED and NET_CAPABILITY_NOT_CONSTRAINED are included
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
 
         // When data is disabled, or data roaming is disabled and the device is roaming, we need
@@ -2483,11 +2484,6 @@
             builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         }
 
-        // mark the network as restricted when service state is non-terrestrial(satellite network)
-        if (mFlags.satelliteInternet() && mIsSatellite) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-        }
-
         // 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.
@@ -2529,6 +2525,23 @@
         builder.setLinkDownstreamBandwidthKbps(mNetworkBandwidth.downlinkBandwidthKbps);
         builder.setLinkUpstreamBandwidthKbps(mNetworkBandwidth.uplinkBandwidthKbps);
 
+        // Configure the network as restricted/constrained for unrestricted satellite network.
+        if (mFlags.satelliteInternet() && mIsSatellite && builder.build()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+            switch (mDataConfigManager.getSatelliteDataSupportMode()) {
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED
+                        -> builder.removeCapability(
+                                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED -> {
+                    try {
+                        builder.removeCapability(DataUtils
+                                .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+                    } catch (Exception ignored) { }
+                }
+                // default case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL
+            }
+        }
+
         NetworkCapabilities nc = builder.build();
         if (mNetworkCapabilities == null || mNetworkAgent == null) {
             // This is the first time when network capabilities is created. The agent is not created
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 008da14..30172db 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -1947,22 +1947,12 @@
         // If the network is satellite, then the network must be restricted.
         if (mFeatureFlags.satelliteInternet()) {
             // The IWLAN data network should remain intact even when satellite is connected.
-            if (dataNetwork.getTransport() != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                // On satellite, every data network needs to be restricted.
-                if (mServiceState.isUsingNonTerrestrialNetwork()
-                        && dataNetwork.getNetworkCapabilities()
-                        .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
-                    evaluation.addDataDisallowedReason(
-                            DataDisallowedReason.DATA_NETWORK_TRANSPORT_NOT_ALLOWED);
-                }
-
-                // Check if the transport is compatible with the network
-                if (mServiceState.isUsingNonTerrestrialNetwork() != dataNetwork.isSatellite()) {
-                    // Since we don't support satellite/cellular network handover, we should always
-                    // tear down the network when transport changes.
-                    evaluation.addDataDisallowedReason(
-                            DataDisallowedReason.DATA_NETWORK_TRANSPORT_NOT_ALLOWED);
-                }
+            if (dataNetwork.getTransport() != AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                    && mServiceState.isUsingNonTerrestrialNetwork() != dataNetwork.isSatellite()) {
+                // Since we don't support satellite/cellular network handover, we should always
+                // tear down the network when transport changes.
+                evaluation.addDataDisallowedReason(
+                        DataDisallowedReason.DATA_NETWORK_TRANSPORT_NOT_ALLOWED);
             }
         }
 
@@ -2171,11 +2161,24 @@
             return true;
         }
 
-        // When the device is on satellite, only restricted network request can request network.
-        if (mServiceState.isUsingNonTerrestrialNetwork()
-                && networkRequest.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
-            return false;
+        // When the device is on satellite, only restricted/constrained network request can request
+        // network.
+        if (mServiceState.isUsingNonTerrestrialNetwork() && networkRequest.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+            switch (mDataConfigManager.getSatelliteDataSupportMode()) {
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED -> {
+                    return false;
+                }
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED -> {
+                    try {
+                        if (networkRequest.hasCapability(DataUtils
+                                .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)) {
+                            return false;
+                        }
+                    } catch (Exception ignored) { }
+                }
+                // default case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL
+            }
         }
 
         // If the network request does not specify cellular or satellite, then it can be
diff --git a/src/java/com/android/internal/telephony/data/DataUtils.java b/src/java/com/android/internal/telephony/data/DataUtils.java
index 8b95913..20da97f 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -59,6 +59,7 @@
  * This class contains all the utility methods used by telephony data stack.
  */
 public class DataUtils {
+    public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
     /** The time format for converting time to readable string. */
     private static final SimpleDateFormat TIME_FORMAT =
             new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
@@ -165,6 +166,7 @@
             case NetworkCapabilities.NET_CAPABILITY_MMTEL -> "MMTEL";
             case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY -> "PRIORITIZE_LATENCY";
             case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH -> "PRIORITIZE_BANDWIDTH";
+            case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED -> "NOT_BANDWIDTH_CONSTRAINED";
             default -> {
                 loge("Unknown network capability(" + netCap + ")");
                 yield "Unknown(" + netCap + ")";
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 9d1c05e..60dcb59 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -5052,18 +5052,84 @@
 
     @Test
     public void testNonTerrestrialNetwork() throws Exception {
+        TelephonyNetworkRequest request;
         mIsNonTerrestrialNetwork = true;
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(false, NetworkCapabilities.NET_CAPABILITY_RCS));
+        // By default, Test only support restricted network, regardless whether constrained.
+        // Verify not_restricted network not supported.
+        request = createNetworkRequest(false, NetworkCapabilities.NET_CAPABILITY_RCS);
+        mDataNetworkControllerUT.addNetworkRequest(request);
         processAllMessages();
         verifyAllDataDisconnected();
+        mDataNetworkControllerUT.removeNetworkRequest(request);
 
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(true, NetworkCapabilities.NET_CAPABILITY_RCS));
+        // Verify restricted network is supported.
+        request = createNetworkRequest(true, NetworkCapabilities.NET_CAPABILITY_RCS);
+        mDataNetworkControllerUT.addNetworkRequest(request);
         processAllMessages();
         verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        getDataNetworks().get(0).tearDown(DataNetwork
+                .TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED);
+
+        // Test constrained network is supported, regardless whether it's restricted
+        processAllFutureMessages();
+        verifyAllDataDisconnected();
+        mCarrierConfig.putInt(CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT,
+                CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED);
+        carrierConfigChanged();
+        // Verify not_restricted, not_constrained network not supported.
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+        try {
+            netCaps.addCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+        } catch (Exception ignored) { }
+        request = new TelephonyNetworkRequest(new NetworkRequest(netCaps,
+                ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
+
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyAllDataDisconnected();
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+
+        // Verify restricted, not_constrained network is supported.
+        netCaps = new NetworkCapabilities();
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        try {
+            netCaps.addCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+        } catch (Exception ignored) { }
+        request = new TelephonyNetworkRequest(new NetworkRequest(netCaps,
+                ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        getDataNetworks().get(0).tearDown(DataNetwork
+                .TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED);
+
+        // TODO(enable after NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED become a default cap)
+        // Test not constrained network supported
+//        processAllFutureMessages();
+//        verifyAllDataDisconnected();
+//        mCarrierConfig.putInt(CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT,
+//                CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL);
+//        carrierConfigChanged();
+//
+//        netCaps = new NetworkCapabilities();
+//        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+//        try {
+//            netCaps.addCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+//        } catch (Exception ignored) {}
+//        mDataNetworkControllerUT.addNetworkRequest(
+//                new TelephonyNetworkRequest(new NetworkRequest(netCaps,
+//                        ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+//                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+//        processAllMessages();
+//        verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
     }
 
     @Test
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 d5a52ea..7795e42 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -53,6 +53,7 @@
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation;
 import android.telephony.Annotation.DataFailureCause;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
 import android.telephony.DataSpecificRegistrationInfo;
 import android.telephony.LteVopsSupportInfo;
@@ -333,33 +334,40 @@
     private void serviceStateChanged(@Annotation.NetworkType int networkType,
             @NetworkRegistrationInfo.RegistrationState int regState,
             DataSpecificRegistrationInfo dsri, boolean isNtn) {
-        ServiceState ss = new ServiceState();
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+        doReturn(isNtn).when(mServiceState).isUsingNonTerrestrialNetwork();
+
+        doReturn(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setAccessNetworkTechnology(networkType)
                 .setRegistrationState(regState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setDataSpecificInfo(dsri)
                 .setIsNonTerrestrialNetwork(isNtn)
-                .build());
+                .build()).when(mServiceState)
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+        doReturn(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
                 .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
                 .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
+                .build()).when(mServiceState)
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+        doReturn(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setAccessNetworkTechnology(networkType)
                 .setRegistrationState(regState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        ss.setDataRoamingFromRegistration(regState
-                == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
+                .build()).when(mServiceState)
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        boolean isRoaming = regState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+        doReturn(isRoaming).when(mServiceState).getDataRoamingFromRegistration();
+        doReturn(isRoaming).when(mServiceState).getDataRoaming();
 
         if (mDataNetworkUT != null) {
             mDataNetworkUT.obtainMessage(9/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
@@ -420,6 +428,8 @@
         doReturn(Set.of(NetworkCapabilities.NET_CAPABILITY_IMS,
                 NetworkCapabilities.NET_CAPABILITY_EIMS, NetworkCapabilities.NET_CAPABILITY_XCAP))
                 .when(mDataConfigManager).getForcedCellularTransportCapabilities();
+        doReturn(CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED)
+                .when(mDataConfigManager).getSatelliteDataSupportMode();
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
         doReturn(true).when(mFeatureFlags).satelliteInternet();
 
@@ -2373,6 +2383,7 @@
     }
 
     private void setupNonTerrestrialDataNetwork() {
+        doReturn(true).when(mServiceState).isUsingNonTerrestrialNetwork();
         NetworkRequestList networkRequestList = new NetworkRequestList();
 
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
@@ -2387,6 +2398,8 @@
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 DataAllowedReason.NORMAL, mDataNetworkCallback);
         processAllMessages();
+
+        assertThat(mDataNetworkUT.isSatellite()).isTrue();
     }
 
     private void setupTerrestrialDataNetwork() {
@@ -2509,6 +2522,42 @@
     }
 
     @Test
+    public void testUnrestrictedSatelliteNetworkCapabilities() {
+        setupNonTerrestrialDataNetwork();
+        assertThat(mDataNetworkUT.isSatellite()).isTrue();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isFalse();
+
+        // Test constrained traffic
+        doReturn(CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED)
+                .when(mDataConfigManager).getSatelliteDataSupportMode();
+        mDataNetworkUT.sendMessage(22/*EVENT_VOICE_CALL_STARTED*/); // update network capabilities
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+        try {
+            assertThat(mDataNetworkUT.getNetworkCapabilities()
+                    .hasCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)).isFalse();
+        } catch (Exception ignored) { }
+
+        // Test not constrained traffic
+        doReturn(CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL)
+                .when(mDataConfigManager).getSatelliteDataSupportMode();
+        mDataNetworkUT.sendMessage(22/*EVENT_VOICE_CALL_STARTED*/); // update network capabilities
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+        // TODO(enable after NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED become a default cap)
+//        try {
+//            assertThat(mDataNetworkUT.getNetworkCapabilities()
+//                    .hasCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)).isTrue();
+//        } catch (Exception ignored) {}
+    }
+
+    @Test
     public void testIsTransportSatelliteSupportNonImsNonTerrestrialNetwork() throws Exception {
         // Service state at Non-terrestrial network
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,