Fix backward compatibility issue for removed wildcard match rule

A template with type MATCH_MOBILE could have empty subscriber
IDs but it should not contain one of subscriber IDs is null.

An app might still build a template through the constructor
which is annotated with @UnsupportedAppUsage. NetworkTemplate
will throw an exception when the app trying to construct a
template with type *_WILDCARD. The constructor should be
backward compatible with old version so this change is to add
the backward compatibility to have NetworkTemplate not throw
an exception.

Bug: 267701889
Test: FrameworksNetTests
Change-Id: I23a607dae508e0c53520e2edf187cb611ed36b68
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index 748b1ba..b26aeaa 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -48,6 +48,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.CollectionUtils;
@@ -70,6 +71,8 @@
  */
 @SystemApi(client = MODULE_LIBRARIES)
 public final class NetworkTemplate implements Parcelable {
+    private static final String TAG = NetworkTemplate.class.getSimpleName();
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "MATCH_" }, value = {
@@ -270,12 +273,17 @@
 
     private static void checkValidMatchSubscriberIds(int matchRule, String[] matchSubscriberIds) {
         switch (matchRule) {
+            // CARRIER templates must always specify a valid subscriber ID.
+            // MOBILE templates can have empty matchSubscriberIds but it must not contain a null
+            // subscriber ID.
             case MATCH_CARRIER:
-                // CARRIER templates must always specify a valid subscriber ID.
                 if (matchSubscriberIds.length == 0) {
                     throw new IllegalArgumentException("checkValidMatchSubscriberIds with empty"
                             + " list of ids for rule" + getMatchRuleName(matchRule));
-                } else if (CollectionUtils.contains(matchSubscriberIds, null)) {
+                }
+                // fall through
+            case MATCH_MOBILE:
+                if (CollectionUtils.contains(matchSubscriberIds, null)) {
                     throw new IllegalArgumentException("checkValidMatchSubscriberIds list of ids"
                             + " may not contain null for rule " + getMatchRuleName(matchRule));
                 }
@@ -296,12 +304,36 @@
         // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
         // to metered networks. It is now possible to match mobile with any meteredness, but
         // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
-        //constructor passes METERED_YES for these types.
-        this(matchRule, new String[] { subscriberId },
+        // constructor passes METERED_YES for these types.
+        // For backwards compatibility, still accept old wildcard match rules (6 and 7 for
+        // MATCH_{MOBILE,WIFI}_WILDCARD) but convert into functionally equivalent non-wildcard
+        // ones.
+        this(getBackwardsCompatibleMatchRule(matchRule),
+                subscriberId != null ? new String[] { subscriberId } : new String[0],
                 wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
-                (matchRule == MATCH_MOBILE || matchRule == MATCH_CARRIER)
-                        ? METERED_YES : METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
-                NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+                getMeterednessForBackwardsCompatibility(matchRule), ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+        if (matchRule == 6 || matchRule == 7) {
+            Log.e(TAG, "Use MATCH_MOBILE with empty subscriberIds or MATCH_WIFI with empty "
+                    + "wifiNetworkKeys instead of template with matchRule=" + matchRule);
+        }
+    }
+
+    private static int getBackwardsCompatibleMatchRule(int matchRule) {
+        // Backwards compatibility old constants
+        // Old MATCH_MOBILE_WILDCARD
+        if (6 == matchRule) return MATCH_MOBILE;
+        // Old MATCH_WIFI_WILDCARD
+        if (7 == matchRule) return MATCH_WIFI;
+        return matchRule;
+    }
+
+    private static int getMeterednessForBackwardsCompatibility(int matchRule) {
+        if (getBackwardsCompatibleMatchRule(matchRule) == MATCH_MOBILE
+                || matchRule == MATCH_CARRIER) {
+            return METERED_YES;
+        }
+        return METERED_ALL;
     }
 
     /** @hide */
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
index 99f1e0b..c2eacbc 100644
--- a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -95,6 +95,13 @@
             NetworkTemplate.Builder(MATCH_CARRIER).build()
         }
 
+        // Verify carrier and mobile template cannot contain one of subscriber Id is null.
+        listOf(MATCH_MOBILE, MATCH_CARRIER).forEach {
+            assertFailsWith<IllegalArgumentException> {
+                NetworkTemplate.Builder(it).setSubscriberIds(setOf(null)).build()
+            }
+        }
+
         // Verify template which matches metered cellular networks,
         // regardless of IMSI. See buildTemplateMobileWildcard.
         NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
@@ -158,6 +165,23 @@
     }
 
     @Test
+    fun testUnsupportedAppUsageConstructor() {
+        val templateMobile = NetworkTemplate(MATCH_MOBILE, null /* subscriberId */,
+                null /* wifiNetworkKey */)
+        val templateMobileWildcard = NetworkTemplate(6 /* MATCH_MOBILE_WILDCARD */,
+                null /* subscriberId */, null /* wifiNetworkKey */)
+        val templateWifiWildcard = NetworkTemplate(7 /* MATCH_WIFI_WILDCARD */,
+                null /* subscriberId */,
+                null /* wifiNetworkKey */)
+
+        assertEquals(NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(),
+                templateMobile)
+        assertEquals(NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build(),
+                templateMobileWildcard)
+        assertEquals(NetworkTemplate.Builder(MATCH_WIFI).build(), templateWifiWildcard)
+    }
+
+    @Test
     fun testBuilderWifiNetworkKeys() {
         // Verify template builder which generates same template with the given different
         // sequence keys.