Support handover rule based on incall

Some carriers do not allow in-call handover such as NR and WiFi
due to network limitation. Adding the incall rule to support it.

Flag: com.android.internal.telephony.flags.incall_handover_policy
Bug: 376765521
Test: atest DataNetworkControllerTest
Change-Id: Ia1e6b367fa212d464d692d7e6b9d8ab429a3c066
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 17d1adb..f3409f8 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -129,3 +129,14 @@
   bug: "366194627"
   is_exported: true
 }
+
+# OWNER=gwenlin TARGET=25Q2
+flag {
+  name: "incall_handover_policy"
+  namespace: "telephony"
+  description: "Support IWLAN handover policy based on incall."
+  bug:"376765521"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 89b0fec..fc029af 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -1269,7 +1269,7 @@
             if (handoverRulesStrings != null) {
                 for (String ruleString : handoverRulesStrings) {
                     try {
-                        mHandoverRuleList.add(new HandoverRule(ruleString));
+                        mHandoverRuleList.add(new HandoverRule(ruleString, mFeatureFlags));
                     } catch (IllegalArgumentException e) {
                         loge("updateHandoverRules: " + e.getMessage());
                     }
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index d5bc741..ddf8fff 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -741,6 +741,8 @@
 
         private static final String RULE_TAG_ROAMING = "roaming";
 
+        private static final String RULE_TAG_INCALL = "incall";
+
         /** Handover rule type. */
         @HandoverRuleType
         public final int type;
@@ -766,6 +768,9 @@
         /** {@code true} indicates this policy is only applicable when the device is roaming. */
         public final boolean isOnlyForRoaming;
 
+        /** {@code true} indicates this policy is only applicable when the device is incall. */
+        public final boolean isOnlyForIncall;
+
         /**
          * Constructor
          *
@@ -773,7 +778,7 @@
          *
          * @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
          */
-        public HandoverRule(@NonNull String ruleString) {
+        public HandoverRule(@NonNull String ruleString, @NonNull FeatureFlags featureFlags) {
             if (TextUtils.isEmpty(ruleString)) {
                 throw new IllegalArgumentException("illegal rule " + ruleString);
             }
@@ -781,6 +786,7 @@
             Set<Integer> source = null, target = null, capabilities = Collections.emptySet();
             int type = 0;
             boolean roaming = false;
+            boolean incall = false;
 
             ruleString = ruleString.trim().toLowerCase(Locale.ROOT);
             String[] expressions = ruleString.split("\\s*,\\s*");
@@ -821,6 +827,11 @@
                         case RULE_TAG_ROAMING:
                             roaming = Boolean.parseBoolean(value);
                             break;
+                        case RULE_TAG_INCALL:
+                            if (featureFlags.incallHandoverPolicy()) {
+                                incall = Boolean.parseBoolean(value);
+                            }
+                            break;
                         default:
                             throw new IllegalArgumentException("unexpected key " + key);
                     }
@@ -867,6 +878,7 @@
             this.type = type;
             networkCapabilities = capabilities;
             isOnlyForRoaming = roaming;
+            isOnlyForIncall = incall;
         }
 
         @Override
@@ -876,8 +888,8 @@
                     .map(AccessNetworkType::toString).collect(Collectors.joining("|"))
                     + ", target=" + targetAccessNetworks.stream().map(AccessNetworkType::toString)
                     .collect(Collectors.joining("|")) + ", isRoaming=" + isOnlyForRoaming
-                    + ", capabilities=" + DataUtils.networkCapabilitiesToString(networkCapabilities)
-                    + "]";
+                    + ", isIncall=" + isOnlyForIncall + ", capabilities="
+                    + DataUtils.networkCapabilitiesToString(networkCapabilities) + "]";
         }
     }
 
@@ -2337,6 +2349,9 @@
             // in data network.
             boolean isRoaming = isWwanInService ? mServiceState.getDataRoamingFromRegistration()
                     : dataNetwork.getLastKnownRoamingState();
+            Phone imsPhone = mPhone.getImsPhone();
+            boolean isIncall = mFeatureFlags.incallHandoverPolicy() && imsPhone != null
+                    && (imsPhone.getCallTracker().getState() != PhoneConstants.State.IDLE);
             int targetAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
                     getDataNetworkType(DataUtils.getTargetTransport(dataNetwork.getTransport())));
             NetworkCapabilities capabilities = dataNetwork.getNetworkCapabilities();
@@ -2344,6 +2359,7 @@
                     + "source=" + AccessNetworkType.toString(sourceAccessNetwork)
                     + ", target=" + AccessNetworkType.toString(targetAccessNetwork)
                     + ", roaming=" + isRoaming
+                    + ", incall=" + isIncall
                     + ", ServiceState=" + mServiceState
                     + ", capabilities=" + capabilities);
 
@@ -2355,6 +2371,11 @@
                     // this rule.
                     continue;
                 }
+                if (rule.isOnlyForIncall && (!mFeatureFlags.incallHandoverPolicy() || !isIncall)) {
+                    // If the rule is for incall only, and the device is not incall, then bypass
+                    // this rule.
+                    continue;
+                }
 
                 if (rule.sourceAccessNetworks.contains(sourceAccessNetwork)
                         && rule.targetAccessNetworks.contains(targetAccessNetwork)) {
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 3f2d6f2..1c5d6f3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -890,6 +890,7 @@
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
         doReturn(true).when(mFeatureFlags).satelliteInternet();
+        doReturn(true).when(mFeatureFlags).incallHandoverPolicy();
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
         doReturn(true).when(mMockPackageManager).hasSystemFeature(anyString());
 
@@ -2375,7 +2376,7 @@
     @Test
     public void testHandoverRuleFromString() {
         HandoverRule handoverRule = new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
-                + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed");
+                + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed", mFeatureFlags);
         assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.GERAN,
                 AccessNetworkType.UTRAN, AccessNetworkType.EUTRAN, AccessNetworkType.NGRAN,
                 AccessNetworkType.IWLAN);
@@ -2387,7 +2388,7 @@
         assertThat(handoverRule.networkCapabilities).isEmpty();
 
         handoverRule = new HandoverRule("source=   NGRAN|     IWLAN, "
-                + "target  =    EUTRAN,    type  =    disallowed ");
+                + "target  =    EUTRAN,    type  =    disallowed ", mFeatureFlags);
         assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.NGRAN,
                 AccessNetworkType.IWLAN);
         assertThat(handoverRule.targetAccessNetworks).containsExactly(AccessNetworkType.EUTRAN);
@@ -2397,7 +2398,7 @@
 
         handoverRule = new HandoverRule("source=   IWLAN, "
                 + "target  =    EUTRAN,    type  =    disallowed, roaming = true,"
-                + " capabilities = IMS | EIMS ");
+                + " capabilities = IMS | EIMS ", mFeatureFlags);
         assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.IWLAN);
         assertThat(handoverRule.targetAccessNetworks).containsExactly(AccessNetworkType.EUTRAN);
         assertThat(handoverRule.type).isEqualTo(HandoverRule.RULE_TYPE_DISALLOWED);
@@ -2406,7 +2407,8 @@
         assertThat(handoverRule.isOnlyForRoaming).isTrue();
 
         handoverRule = new HandoverRule("source=EUTRAN|NGRAN|IWLAN|UNKNOWN, "
-                + "target=EUTRAN|NGRAN|IWLAN, type=disallowed, capabilities = IMS|EIMS");
+                + "target=EUTRAN|NGRAN|IWLAN, type=disallowed, capabilities = IMS|EIMS",
+                mFeatureFlags);
         assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.EUTRAN,
                 AccessNetworkType.NGRAN, AccessNetworkType.IWLAN, AccessNetworkType.UNKNOWN);
         assertThat(handoverRule.targetAccessNetworks).containsExactly(AccessNetworkType.EUTRAN,
@@ -2415,44 +2417,61 @@
         assertThat(handoverRule.networkCapabilities).containsExactly(
                 NetworkCapabilities.NET_CAPABILITY_IMS, NetworkCapabilities.NET_CAPABILITY_EIMS);
 
-        assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("V2hhdCBUaGUgRnVjayBpcyB0aGlzIQ=="));
+        handoverRule = new HandoverRule("source=NGRAN|IWLAN, "
+                + "target  =    NGRAN|IWLAN,    type=disallowed, incall = true,"
+                + " capabilities = IMS|EIMS ", mFeatureFlags);
+        assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.NGRAN,
+                AccessNetworkType.IWLAN);
+        assertThat(handoverRule.sourceAccessNetworks).containsExactly(AccessNetworkType.NGRAN,
+                AccessNetworkType.IWLAN);
+        assertThat(handoverRule.type).isEqualTo(HandoverRule.RULE_TYPE_DISALLOWED);
+        assertThat(handoverRule.networkCapabilities).containsExactly(
+                NetworkCapabilities.NET_CAPABILITY_IMS, NetworkCapabilities.NET_CAPABILITY_EIMS);
+        assertThat(handoverRule.isOnlyForIncall).isTrue();
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"));
+                () -> new HandoverRule("V2hhdCBUaGUgRnVjayBpcyB0aGlzIQ==", mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"));
+                () -> new HandoverRule("target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed",
+                                       mFeatureFlags));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> new HandoverRule("source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed",
+                                        mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
                 () -> new HandoverRule("source=GERAN, target=UNKNOWN, type=disallowed, "
-                        + "capabilities=IMS"));
+                        + "capabilities=IMS", mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
                 () -> new HandoverRule("source=UNKNOWN, target=IWLAN, type=allowed, "
-                        + "capabilities=IMS"));
+                        + "capabilities=IMS", mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=GERAN, target=IWLAN, type=wtf"));
+                () -> new HandoverRule("source=GERAN, target=IWLAN, type=wtf", mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=GERAN, target=NGRAN, type=allowed"));
+                () -> new HandoverRule("source=GERAN, target=NGRAN, type=allowed", mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=IWLAN, target=WTFRAN, type=allowed"));
+                () -> new HandoverRule("source=IWLAN, target=WTFRAN, type=allowed",
+                                       mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=IWLAN, target=|, type=allowed"));
+                () -> new HandoverRule("source=IWLAN, target=|, type=allowed", mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=GERAN, target=IWLAN, type=allowed, capabilities=|"));
+                () -> new HandoverRule("source=GERAN, target=IWLAN, type=allowed, capabilities=|",
+                                        mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
-                () -> new HandoverRule("source=GERAN, target=IWLAN, type=allowed, capabilities="));
+                () -> new HandoverRule("source=GERAN, target=IWLAN, type=allowed, capabilities=",
+                                        mFeatureFlags));
 
         assertThrows(IllegalArgumentException.class,
                 () -> new HandoverRule("source=GERAN, target=IWLAN, type=allowed, "
-                        + "capabilities=wtf"));
+                        + "capabilities=wtf", mFeatureFlags));
     }
 
     @Test
@@ -2920,6 +2939,35 @@
     }
 
     @Test
+    public void testHandoverDataNetworkNotAllowedByIncallPolicy() throws Exception {
+        mCarrierConfig.putStringArray(CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY,
+                new String[]{"source=EUTRAN, target=IWLAN, type=disallowed, incall=true, "
+                        + "capabilities=IMS"});
+        carrierConfigChanged();
+        testSetupImsDataNetwork();
+        doReturn(PhoneConstants.State.RINGING).when(mCT).getState();
+
+        // After this, IMS data network should be disconnected, and DNC should attempt to
+        // establish a new one on IWLAN
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Verify all data disconnected.
+        verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
+        verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
+
+        // A new data network should be connected on IWLAN
+        List<DataNetwork> dataNetworkList = getDataNetworks();
+        assertThat(dataNetworkList).hasSize(1);
+        assertThat(dataNetworkList.get(0).isConnected()).isTrue();
+        assertThat(dataNetworkList.get(0).getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_IMS)).isTrue();
+        assertThat(dataNetworkList.get(0).getTransport())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+    }
+
+    @Test
     public void testHandoverDataNetworkRetry() throws Exception {
         testSetupImsDataNetwork();