Force MMS on IWLAN when needed

Remove MMS capability on cellular network when the
following conditions met,

1. QNS prefers MMS on IWLAN.
2. Carrier has another APN that can support MMS on IWLAN.

This will force MMS to be brought up on IWLAN when MMS
request arrives later.

When the condition is not met, the MMS capability will be
added back to the cellular network.

Fix: 316211526
Test: Basic telephony functionality tests
Test: atest DataNetworkTest
Test: Manually tested MMS with T-Mobile wifi-calling
Change-Id: I85b9dedcea4cb685cb45c98e1ceba52be7811260
diff --git a/flags/data.aconfig b/flags/data.aconfig
index daddc2e..cad7da7 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -104,3 +104,10 @@
   description: "The DataCallSessionStats metrics will capture whether the IWLAN PDN is set up on cross-SIM calling."
   bug: "313956117"
 }
+
+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"
+}
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 7c500f8..d52a3b4 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -31,6 +31,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkProvider;
+import android.net.NetworkRequest;
 import android.net.NetworkScore;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
@@ -91,6 +92,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RIL;
+import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
@@ -743,6 +745,11 @@
     private @Nullable Consumer<Integer> mNetworkValidationResultCodeCallback;
 
     /**
+     * Callback used to listen QNS preference changes.
+     */
+    private @Nullable AccessNetworksManagerCallback mAccessNetworksManagerCallback;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -1172,6 +1179,23 @@
                         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) {
+                            if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) {
+                                log("MMS preference changed.");
+                                updateNetworkCapabilities();
+                            }
+                        }
+                    };
+                    mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
+                }
+            }
+
             // Only add symmetric code here, for example, registering and unregistering.
             // DefaultState.enter() is the starting point in the life cycle of the DataNetwork,
             // and DefaultState.exit() is the end. For non-symmetric initializing works, put them
@@ -1181,6 +1205,10 @@
         @Override
         public void exit() {
             logv("Unregistering all events.");
+            if (mFlags.forceIwlanMms() && mAccessNetworksManagerCallback != null) {
+                mAccessNetworksManager.unregisterCallback(mAccessNetworksManagerCallback);
+            }
+
             // Check null for devices not supporting FEATURE_TELEPHONY_IMS.
             if (mPhone.getImsPhone() != null) {
                 mPhone.getImsPhone().getCallTracker().unregisterForVoiceCallStarted(getHandler());
@@ -2333,6 +2361,35 @@
             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.
+        if (mFlags.forceIwlanMms() && 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.
+            if (mAccessNetworksManager.getPreferredTransportByNetworkCapability(
+                    NetworkCapabilities.NET_CAPABILITY_MMS)
+                    == AccessNetworkConstants.TRANSPORT_TYPE_WLAN && mTransport
+                    == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+
+                DataProfile dataProfile = mDataNetworkController.getDataProfileManager()
+                        .getDataProfileForNetworkRequest(new TelephonyNetworkRequest(
+                                new NetworkRequest.Builder().addCapability(
+                                NetworkCapabilities.NET_CAPABILITY_MMS).build(), mPhone),
+                        TelephonyManager.NETWORK_TYPE_IWLAN, false, false, false);
+                // If we find another data data profile that can support MMS on IWLAN, then remove
+                // the MMS capability from this cellular network. This will allow IWLAN to be
+                // brought up for MMS later.
+                if (dataProfile != null && !dataProfile.equals(mDataProfile)) {
+                    log("Found a different data profile " + mDataProfile.getApn()
+                            + " that can serve MMS on IWLAN.");
+                    builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+                }
+            }
+        }
+
         // If one of the capabilities are for special use, for example, IMS, CBS, then this
         // network should be restricted, regardless data is enabled or not.
         if (NetworkCapabilitiesUtils.inferRestrictedCapability(builder.build())
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 edeb191..ad1ba61 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -76,6 +76,7 @@
 
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
 import com.android.internal.telephony.data.DataNetwork.DataNetworkCallback;
@@ -123,7 +124,7 @@
             .setApnName("fake_apn")
             .setUser("user")
             .setPassword("passwd")
-            .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL)
+            .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_MMS)
             .setProtocol(ApnSetting.PROTOCOL_IPV6)
             .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
             .setCarrierEnabled(true)
@@ -134,6 +135,18 @@
             .setMaxConnsTime(789)
             .build();
 
+    private final ApnSetting mMmsApnSetting = new ApnSetting.Builder()
+            .setId(2164)
+            .setOperatorNumeric("12345")
+            .setEntryName("fake_mms_apn")
+            .setApnName("fake_mms_apn")
+            .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+            .setProtocol(ApnSetting.PROTOCOL_IPV6)
+            .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+            .setCarrierEnabled(true)
+            .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN)
+            .build();
+
     private final ApnSetting mImsApnSetting = new ApnSetting.Builder()
             .setId(2163)
             .setOperatorNumeric("12345")
@@ -157,6 +170,11 @@
             .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
             .build();
 
+    private final DataProfile mMmsDataProfile = new DataProfile.Builder()
+            .setApnSetting(mMmsApnSetting)
+            .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
+            .build();
+
     private final DataProfile mImsDataProfile = new DataProfile.Builder()
             .setApnSetting(mImsApnSetting)
             .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
@@ -2203,4 +2221,43 @@
                 mDataNetworkUT, mDataCallSessionStats);
         processAllMessages();
     }
+
+    @Test
+    public void testMmsCapabilityRemovedWhenMmsPreferredOnIwlan() throws Exception {
+        doReturn(true).when(mFeatureFlags).forceIwlanMms();
+        setupDataNetwork();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
+
+        ArgumentCaptor<AccessNetworksManagerCallback> accessNetworksManagerCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(AccessNetworksManagerCallback.class);
+        verify(mAccessNetworksManager).registerCallback(
+                accessNetworksManagerCallbackArgumentCaptor.capture());
+
+        // Now QNS prefers MMS on IWLAN
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        doReturn(mMmsDataProfile).when(mDataProfileManager).getDataProfileForNetworkRequest(
+                any(TelephonyNetworkRequest.class),
+                    eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(false), eq(false), eq(false));
+        accessNetworksManagerCallbackArgumentCaptor.getValue()
+                .onPreferredTransportChanged(NetworkCapabilities.NET_CAPABILITY_MMS);
+        processAllMessages();
+
+        // Check if MMS capability is removed.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isFalse();
+
+        // Now QNS prefers MMS on IWLAN
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager)
+            .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        accessNetworksManagerCallbackArgumentCaptor.getValue()
+                .onPreferredTransportChanged(NetworkCapabilities.NET_CAPABILITY_MMS);
+        processAllMessages();
+
+        // Check if MMS capability is added back.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
+    }
 }