Merge "Support checking DnsResolver version in isFeatureSupported." into main
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index 0426ace..04ce2fa 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -22,6 +22,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.DNS_RESOLVER_MODULE_ID;
 import static com.android.net.module.util.FeatureVersions.MODULE_MASK;
 import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
 import static com.android.net.module.util.FeatureVersions.VERSION_MASK;
@@ -68,7 +69,8 @@
     @VisibleForTesting
     public static void resetPackageVersionCacheForTest() {
         sPackageVersion = -1;
-        sModuleVersion = -1;
+        sTetheringModuleVersion = -1;
+        sResolvModuleVersion = -1;
         sNetworkStackModuleVersion = -1;
     }
 
@@ -243,23 +245,23 @@
         }
     }
 
-    // Guess the tethering module name based on the package prefix of the connectivity resources
-    // Take the resource package name, cut it before "connectivity" and append "tethering".
+    // Guess an APEX module name based on the package prefix of the connectivity resources
+    // Take the resource package name, cut it before "connectivity" and append the module name.
     // Then resolve that package version number with packageManager.
-    // If that fails retry by appending "go.tethering" instead
-    private static long resolveTetheringModuleVersion(@NonNull Context context)
+    // If that fails retry by appending "go.<moduleName>" instead.
+    private static long resolveApexModuleVersion(@NonNull Context context, String moduleName)
             throws PackageManager.NameNotFoundException {
         final String pkgPrefix = resolvePkgPrefix(context);
         final PackageManager packageManager = context.getPackageManager();
         try {
-            return packageManager.getPackageInfo(pkgPrefix + "tethering",
+            return packageManager.getPackageInfo(pkgPrefix + moduleName,
                     PackageManager.MATCH_APEX).getLongVersionCode();
         } catch (PackageManager.NameNotFoundException e) {
             Log.d(TAG, "Device is using go modules");
             // fall through
         }
 
-        return packageManager.getPackageInfo(pkgPrefix + "go.tethering",
+        return packageManager.getPackageInfo(pkgPrefix + "go." + moduleName,
                 PackageManager.MATCH_APEX).getLongVersionCode();
     }
 
@@ -274,19 +276,35 @@
         return connResourcesPackage.substring(0, pkgPrefixLen);
     }
 
-    private static volatile long sModuleVersion = -1;
+    private static volatile long sTetheringModuleVersion = -1;
+
     private static long getTetheringModuleVersion(@NonNull Context context) {
-        if (sModuleVersion >= 0) return sModuleVersion;
+        if (sTetheringModuleVersion >= 0) return sTetheringModuleVersion;
 
         try {
-            sModuleVersion = resolveTetheringModuleVersion(context);
+            sTetheringModuleVersion = resolveApexModuleVersion(context, "tethering");
         } catch (PackageManager.NameNotFoundException e) {
             // It's expected to fail tethering module version resolution on the devices with
             // flattened apex
             Log.e(TAG, "Failed to resolve tethering module version: " + e);
             return DEFAULT_PACKAGE_VERSION;
         }
-        return sModuleVersion;
+        return sTetheringModuleVersion;
+    }
+
+    private static volatile long sResolvModuleVersion = -1;
+    private static long getResolvModuleVersion(@NonNull Context context) {
+        if (sResolvModuleVersion >= 0) return sResolvModuleVersion;
+
+        try {
+            sResolvModuleVersion = resolveApexModuleVersion(context, "resolv");
+        } catch (PackageManager.NameNotFoundException e) {
+            // It's expected to fail resolv module version resolution on the devices with
+            // flattened apex
+            Log.e(TAG, "Failed to resolve resolv module version: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
+        return sResolvModuleVersion;
     }
 
     private static volatile long sNetworkStackModuleVersion = -1;
@@ -342,6 +360,8 @@
             moduleVersion = getTetheringModuleVersion(context);
         } else if (moduleId == NETWORK_STACK_MODULE_ID) {
             moduleVersion = getNetworkStackModuleVersion(context);
+        } else if (moduleId == DNS_RESOLVER_MODULE_ID) {
+            moduleVersion = getResolvModuleVersion(context);
         } else {
             throw new IllegalArgumentException("Unknown module " + moduleId);
         }
diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
index d5f8124..d0cf3fd 100644
--- a/staticlibs/device/com/android/net/module/util/FeatureVersions.java
+++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
@@ -37,6 +37,7 @@
     public static final long VERSION_MASK = 0x00F_FFFF_FFFFL;
     public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT;
     public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT;
+    public static final long DNS_RESOLVER_MODULE_ID = 0x03L << MODULE_SHIFT;
     // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system
     // try to add a NAT-T keepalive packet filter with v6 address, introduced in version
     // M-2023-Sept on July 3rd, 2023.
@@ -48,4 +49,11 @@
     // by BPF for the given uid and conditions, introduced in version M-2024-Feb on Nov 6, 2023.
     public static final long FEATURE_IS_UID_NETWORKING_BLOCKED =
             CONNECTIVITY_MODULE_ID + 34_14_00_000L;
+
+    // DDR is a feature implemented across NetworkStack, ConnectivityService and DnsResolver.
+    // The flag that enables this feature is in NetworkStack.
+    public static final long FEATURE_DDR_IN_CONNECTIVITY =
+            CONNECTIVITY_MODULE_ID + 35_11_00_000L;
+    public static final long FEATURE_DDR_IN_DNSRESOLVER =
+            DNS_RESOLVER_MODULE_ID + 35_11_00_000L;
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 9fb61d9..a5af09b 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -24,6 +24,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.DNS_RESOLVER_MODULE_ID;
 import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
 
 import static org.junit.Assert.assertEquals;
@@ -77,7 +78,9 @@
     private static final int TEST_DEFAULT_FLAG_VALUE = 0;
     private static final int TEST_MAX_FLAG_VALUE = 1000;
     private static final int TEST_MIN_FLAG_VALUE = 100;
-    private static final long TEST_PACKAGE_VERSION = 290000000;
+    private static final long TEST_PACKAGE_VERSION = 290500000;
+    private static final long TEST_GO_PACKAGE_VERSION = 290000000;  // Not updated
+    private static final long TEST_RESOLV_PACKAGE_VERSION = 290300000;  // Updated, but older.
     private static final String TEST_PACKAGE_NAME = "test.package.name";
     // The APEX name is the name of the APEX module, as in android.content.pm.ModuleInfo, and is
     // used for its mount point in /apex. APEX packages are actually APKs with a different
@@ -85,14 +88,18 @@
     // that manifest, and is reflected in android.content.pm.ApplicationInfo. Contrary to the APEX
     // (module) name, different package names are typically used to identify the organization that
     // built and signed the APEX modules.
-    private static final String TEST_APEX_PACKAGE_NAME = "com.prefix.android.tethering";
-    private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering";
+    private static final String TEST_TETHERING_PACKAGE_NAME = "com.prefix.android.tethering";
+    private static final String TEST_GO_TETHERING_PACKAGE_NAME = "com.prefix.android.go.tethering";
+    private static final String TEST_RESOLV_PACKAGE_NAME = "com.prefix.android.resolv";
+    private static final String TEST_GO_RESOLV_PACKAGE_NAME = "com.prefix.android.go.resolv";
     private static final String TEST_CONNRES_PACKAGE_NAME =
             "com.prefix.android.connectivity.resources";
     private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack";
     private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack";
     private final PackageInfo mPackageInfo = new PackageInfo();
-    private final PackageInfo mApexPackageInfo = new PackageInfo();
+    private final PackageInfo mGoApexPackageInfo = new PackageInfo();
+    private final PackageInfo mTetheringApexPackageInfo = new PackageInfo();
+    private final PackageInfo mResolvApexPackageInfo = new PackageInfo();
     private MockitoSession mSession;
 
     @Mock private Context mContext;
@@ -105,13 +112,22 @@
         mSession = mockitoSession().spyStatic(DeviceConfig.class).startMocking();
 
         mPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
-        mApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        mTetheringApexPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        mGoApexPackageInfo.setLongVersionCode(TEST_GO_PACKAGE_VERSION);
+        mResolvApexPackageInfo.setLongVersionCode(TEST_RESOLV_PACKAGE_VERSION);
 
         doReturn(mPm).when(mContext).getPackageManager();
         doReturn(TEST_PACKAGE_NAME).when(mContext).getPackageName();
         doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
         doReturn(mPackageInfo).when(mPm).getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt());
-        doReturn(mApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_APEX_PACKAGE_NAME), anyInt());
+        doReturn(mTetheringApexPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_TETHERING_PACKAGE_NAME), anyInt());
+        doReturn(mResolvApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_RESOLV_PACKAGE_NAME),
+                anyInt());
+        doReturn(mGoApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_GO_TETHERING_PACKAGE_NAME),
+                anyInt());
+        doReturn(mGoApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_GO_RESOLV_PACKAGE_NAME),
+                anyInt());
 
         doReturn(mResources).when(mContext).getResources();
 
@@ -342,9 +358,9 @@
     @Test
     public void testFeatureIsEnabledOnGo() throws Exception {
         doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(
-                eq(TEST_APEX_PACKAGE_NAME), anyInt());
-        doReturn(mApexPackageInfo).when(mPm).getPackageInfo(
-                eq(TEST_GO_APEX_PACKAGE_NAME), anyInt());
+                eq(TEST_TETHERING_PACKAGE_NAME), anyInt());
+        doReturn(mTetheringApexPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_GO_TETHERING_PACKAGE_NAME), anyInt());
         doReturn("0").when(() -> DeviceConfig.getProperty(
                 NAMESPACE_CONNECTIVITY, TEST_EXPERIMENT_FLAG));
         doReturn("0").when(() -> DeviceConfig.getProperty(
@@ -483,6 +499,31 @@
                 mContext, 889900000L + CONNECTIVITY_MODULE_ID));
     }
 
+
+    @Test
+    public void testIsFeatureSupported_resolvFeature() throws Exception {
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_RESOLV_PACKAGE_VERSION + DNS_RESOLVER_MODULE_ID));
+        // Return false because feature requires a future version.
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, 889900000L + DNS_RESOLVER_MODULE_ID));
+    }
+
+    @Test
+    public void testIsFeatureSupported_goResolvFeature() throws Exception {
+        doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(eq(TEST_RESOLV_PACKAGE_NAME),
+                anyInt());
+        doReturn(mGoApexPackageInfo).when(mPm).getPackageInfo(eq(TEST_GO_RESOLV_PACKAGE_NAME),
+                anyInt());
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_RESOLV_PACKAGE_VERSION + DNS_RESOLVER_MODULE_ID));
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_GO_PACKAGE_VERSION + DNS_RESOLVER_MODULE_ID));
+        // Return false because feature requires a future version.
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, 889900000L + DNS_RESOLVER_MODULE_ID));
+    }
+
     @Test
     public void testIsFeatureSupported_illegalModule() throws Exception {
         assertThrows(IllegalArgumentException.class,