Merge "Address warnings in TestableNetworkCallback" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 13a7a22..d1cc79d 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -16,7 +16,7 @@
 
 package com.android.networkstack.tethering;
 
-import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE;
+import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_DUMP_RECV_BUFSIZE;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 
@@ -200,7 +200,7 @@
             final FileDescriptor fd;
             try {
                 fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER,
-                        SOCKET_RECV_BUFSIZE);
+                        SOCKET_DUMP_RECV_BUFSIZE);
             } catch (ErrnoException e) {
                 mLog.e("Unable to create conntrack socket " + e);
                 return null;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 4bbe130..771a9ff 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -149,6 +149,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.FrameworkConnectivityStatsLog;
 import com.android.net.module.util.HandlerUtils;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.RoutingCoordinatorManager;
@@ -1718,6 +1719,12 @@
         // After T, tethering always trust the iface pass by state change intent. This allow
         // tethering to deprecate tetherable p2p regexs after T.
         final int type = SdkLevel.isAtLeastT() ? TETHERING_WIFI_P2P : ifaceNameToType(ifname);
+        if (type != TETHERING_WIFI_P2P) {
+            FrameworkConnectivityStatsLog.write(
+                    FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
+                    FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WIFIP2P_TYPE_MISMATCH);
+        }
+
         if (!checkTetherableType(type)) {
             mLog.e(ifname + " is not a tetherable iface, ignoring");
             return;
@@ -1768,6 +1775,11 @@
                 mLog.e("Cannot enable IP serving in unknown WiFi mode: " + wifiIpMode);
                 return;
         }
+        if (type != TETHERING_WIFI) {
+            FrameworkConnectivityStatsLog.write(
+                    FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED,
+                    FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WIFI_TYPE_MISMATCH);
+        }
 
         // After T, tethering always trust the iface pass by state change intent. This allow
         // tethering to deprecate tetherable wifi regexs after T.
diff --git a/bpf/headers/include/bpf_helpers.h b/bpf/headers/include/bpf_helpers.h
index fe64538..8526745 100644
--- a/bpf/headers/include/bpf_helpers.h
+++ b/bpf/headers/include/bpf_helpers.h
@@ -155,22 +155,45 @@
 
 // Helpers for writing sdk level specific bpf programs
 //
-// Note: we choose to follow sdk api level values, but there is no real need for this:
-// These just need to be monotonically increasing.  We could also use values ten or even
-// a hundred times larger to leave room for quarters or months.  We may also just use
-// dates or something (2502 or 202506 for 25Q2) or even the mainline bpfloader version...
+// Note: we choose to follow 'ro.build.version.sdk_full'
+// (or just 'sdk' if 'sdk_full' is not available) values,
+// multiplied by 100, with 1 added per QPR.
+// This will (eventually) match our bpfloader versioning scheme.
+//
+// This is just for ease of use, really these are only
+// ever compared to each other, so they only need to be
+// monotonically increasing.
+//
 // For now this easily suffices for our use case.
+//
+// Note: 24Q1 is the first trunk stable release,
+// and thus where quarters start possibly mattering.
+//
+// We leave most of these as commented out documentation,
+// as it's probably a bad idea to actually use them.
 
 struct sdk_level_uint { unsigned int sdk_level; };
 #define SDK_LEVEL_(v) ((struct sdk_level_uint){ .sdk_level = (v) })
-#define SDK_LEVEL_NONE SDK_LEVEL_(0)
-#define SDK_LEVEL_S    SDK_LEVEL_(31) // Android 12
-#define SDK_LEVEL_Sv2  SDK_LEVEL_(32) // Android 12L
-#define SDK_LEVEL_T    SDK_LEVEL_(33) // Android 13
-#define SDK_LEVEL_U    SDK_LEVEL_(34) // Android 14
-#define SDK_LEVEL_V    SDK_LEVEL_(35) // Android 15
-#define SDK_LEVEL_24Q3 SDK_LEVEL_V
-#define SDK_LEVEL_25Q2 SDK_LEVEL_(36) // Android 16
+//      SDK_LEVEL_NONE   SDK_LEVEL_(0)    // mainline implies S+
+#define SDK_LEVEL_S      SDK_LEVEL_(3100) // Android 12     [31]
+//      SDK_LEVEL_Sv2    SDK_LEVEL_(3200) // Android 12L    [32]
+#define SDK_LEVEL_T      SDK_LEVEL_(3300) // Android 13     [33]
+#define SDK_LEVEL_U      SDK_LEVEL_(3400) // Android 14/U   [34]
+//      SDK_LEVEL_U_QPR1 SDK_LEVEL_(3401) // Android 14/U QPR1
+//      SDK_LEVEL_24Q1   SDK_LEVEL_(3402) // Android 14/U QPR2
+//      SDK_LEVEL_24Q2   SDK_LEVEL_(3403) // Android 14/U QPR3
+#define SDK_LEVEL_24Q3   SDK_LEVEL_(3500) // Android 15/V   [35]
+//      SDK_LEVEL_24Q4   SDK_LEVEL_(3501) // Android 15/V QPR1
+//      SDK_LEVEL_25Q1   SDK_LEVEL_(3502) // Android 15/V QPR2
+#define SDK_LEVEL_25Q2   SDK_LEVEL_(3600) // Android 16 (B) [36.0]
+//      SDK_LEVEL_25Q3   SDK_LEVEL_(3601) // Android 16 QPR
+#define SDK_LEVEL_25Q4   SDK_LEVEL_(3610) // Android 16.1   [36.1]
+//      SDK_LEVEL_26Q1   SDK_LEVEL_(3611) // Android 16.1 QPR
+#define SDK_LEVEL_26Q2   SDK_LEVEL_(3700) // Android 17 (C) [37.0]
+//      SDK_LEVEL_26Q3   SDK_LEVEL_(3701) // Android 17 QPR
+#define SDK_LEVEL_26Q4   SDK_LEVEL_(3710) // Android 17.1   [37.1]
+//      SDK_LEVEL_27Q1   SDK_LEVEL_(3711) // Android 17.1 QPR
+#define SDK_LEVEL_27Q2   SDK_LEVEL_(3800) // Android 18     [38.0]
 
 #define SDK_LEVEL_IS_AT_LEAST(lvl, v) ((lvl).sdk_level >= (SDK_LEVEL_##v).sdk_level)
 
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 07375c7..0a74857 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -4175,27 +4175,27 @@
         // Since mApps in PermissionMonitor needs to be populated first to ensure that
         // listening network request which is sent by MultipathPolicyTracker won't be added
         // NET_CAPABILITY_FOREGROUND capability. Thus, MultipathPolicyTracker.start() must
-        // be called after PermissionMonitor#startMonitoring().
-        // Calling PermissionMonitor#startMonitoring() in systemReadyInternal() and the
+        // be called after PermissionMonitor#initialize().
+        // Calling PermissionMonitor#initialize() in systemReadyInternal() and the
         // MultipathPolicyTracker.start() is called in NetworkPolicyManagerService#systemReady()
         // to ensure the tracking will be initialized correctly.
-        final ConditionVariable startMonitoringDone = new ConditionVariable();
+        final ConditionVariable permissionMonitorInitializeDone = new ConditionVariable();
         mHandler.post(() -> {
             mPermissionMonitor.initialize();
             // Calling mBroadcastReceiveHelper.callCallbackForInitialUsers() after
-            // PermissionMonitor.startMonitoring() ensures that the internal lists
+            // PermissionMonitor#initialize() ensures that the internal lists
             // (mUidsAllowedOnRestrictedNetworks and mUsersTrafficPermissions) in
             // PermissionMonitor are prepared before processing initial users.
             // While technically the onUserAdded callback (triggered by
             // callCallbackForInitialUsers) handles sending network and traffic
             // permissions to netd and bpf, which depend on these lists, moving
-            // this call before startMonitoring would necessitate performing these
-            // actions again within startMonitoring, leading to redundant work.
+            // this call before initialize would necessitate performing these
+            // actions again within initialize, leading to redundant work.
             // Therefore, keeping callCallbackForInitialUsers() in this order is the
             // safest approach to avoid duplicated operations and ensure the
             // permission lists are ready when the initial user callbacks are invoked.
             mBroadcastReceiveHelper.callOnUserAddedForExistingUsers();
-            startMonitoringDone.open();
+            permissionMonitorInitializeDone.open();
         });
         mProxyTracker.loadGlobalProxy();
         registerDnsResolverUnsolicitedEventListener();
@@ -4239,9 +4239,9 @@
                 CONNECTIVITY_STATE_SAMPLE, this::sampleConnectivityStateToStatsEvent);
         // Wait PermissionMonitor to finish the permission update. Then MultipathPolicyTracker won't
         // have permission problem. While CV#block() is unbounded in time and can in principle block
-        // forever, this replaces a synchronous call to PermissionMonitor#startMonitoring, which
+        // forever, this replaces a synchronous call to PermissionMonitor#initialize, which
         // could have blocked forever too.
-        startMonitoringDone.block();
+        permissionMonitorInitializeDone.block();
     }
 
     /**
diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
index 60d27c3..9b15728 100644
--- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -20,6 +20,7 @@
 import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
 import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
@@ -52,6 +53,8 @@
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.HandlerUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -87,10 +90,23 @@
         }
     }
 
-    private enum AvoidBadWifiSource {
-        FROM_RESOURCE,
-        FROM_CARRIER_CONFIG,
-    }
+    /**
+     * Indicates that the "Avoid Bad Wi-Fi" setting originates from a resource.
+     */
+    private static final int FROM_RESOURCE = 0;
+
+    /**
+     * Indicates that the "Avoid Bad Wi-Fi" setting originates from carrier configuration.
+     */
+    private static final int FROM_CARRIER_CONFIG = 1;
+
+    /**
+     * Defines the set of possible integer constants for AvoidBadWifiSource.
+     * This annotation provides compile-time type safety.
+     */
+    @IntDef({FROM_RESOURCE, FROM_CARRIER_CONFIG})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AvoidBadWifiSource {}
 
     private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
 
@@ -105,8 +121,7 @@
     private final ContentResolver mResolver;
     private final SettingObserver mSettingObserver;
     private final BroadcastReceiver mBroadcastReceiver;
-    private final boolean mAvoidBadWifiFromCarrierConfigFeature;
-    private final boolean mHasTelephonySubscription;
+    private final @AvoidBadWifiSource int mAvoidBadWifiSource;
     // This will be null if the FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG is off
     private final @Nullable CarrierConfigManager mCarrierConfigManager;
     private final @Nullable CarrierConfigChangeListener mCarrierConfigChangeListener;
@@ -162,15 +177,19 @@
 
         @VisibleForTesting
         protected boolean readAvoidBadWifiFromCarrierConfig(
-                @NonNull final Context context, final int subId) {
+                @Nullable final CarrierConfigManager ccm, final int subId) {
             // Defaults to true to avoid potentially poor Wi-Fi and improve user experience.
             final boolean defaultConfig = true;
             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                 return defaultConfig;
             }
 
+            // It implies the FEATURE_TELEPHONY_SUBSCRIPTION is not supported, return default config
+            if (ccm == null) {
+                return defaultConfig;
+            }
+
             PersistableBundle config = null;
-            final CarrierConfigManager ccm = context.getSystemService(CarrierConfigManager.class);
 
             try {
                 config = ccm.getConfigForSubId(subId,
@@ -271,18 +290,17 @@
             }
         };
 
-        mHasTelephonySubscription =
-            mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION);
-        mAvoidBadWifiFromCarrierConfigFeature = mDeps.getAvoidBadWifiFromCarrierConfigFeature();
-        mCarrierConfigManager = mAvoidBadWifiFromCarrierConfigFeature
-            ? mContext.getSystemService(CarrierConfigManager.class)
-            : null;
-
-        mCarrierConfigChangeListener = mAvoidBadWifiFromCarrierConfigFeature
-            ? new CarrierConfigChangeListener()
-            : null;
-
-        if (!mAvoidBadWifiFromCarrierConfigFeature) {
+        if (mDeps.getAvoidBadWifiFromCarrierConfigFeature()) {
+            mAvoidBadWifiSource = FROM_CARRIER_CONFIG;
+            mCarrierConfigManager =
+                mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION)
+                ? mContext.getSystemService(CarrierConfigManager.class)
+                : null;
+            mCarrierConfigChangeListener = new CarrierConfigChangeListener();
+        } else {
+            mAvoidBadWifiSource = FROM_RESOURCE;
+            mCarrierConfigManager = null;
+            mCarrierConfigChangeListener = null;
             updateAvoidBadWifi();
             updateMeteredMultipathPreference();
         }
@@ -304,7 +322,7 @@
         mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
                 handlerExecutor, new ActiveDataSubscriptionIdListener());
 
-        if (mAvoidBadWifiFromCarrierConfigFeature) {
+        if (mCarrierConfigManager != null) {
             mCarrierConfigManager.registerCarrierConfigChangeListener(
                     BackgroundThread.getExecutor(), mCarrierConfigChangeListener
             );
@@ -325,7 +343,7 @@
         mResolver.unregisterContentObserver(mSettingObserver);
 
         mContext.unregisterReceiver(mBroadcastReceiver);
-        if (mAvoidBadWifiFromCarrierConfigFeature) {
+        if (mCarrierConfigManager != null) {
             mCarrierConfigManager.unregisterCarrierConfigChangeListener(
                     mCarrierConfigChangeListener
             );
@@ -463,18 +481,6 @@
     }
 
     /**
-     * Determines the source of the avoid bad Wi-Fi setting.
-     * It returns FROM_CARRIER_CONFIG if the carrier config feature is enabled,
-     * otherwise it returns FROM_RESOURCE.
-     *
-     */
-    private AvoidBadWifiSource getAvoidBadWifiSource() {
-        return mAvoidBadWifiFromCarrierConfigFeature
-            ? AvoidBadWifiSource.FROM_CARRIER_CONFIG
-            : AvoidBadWifiSource.FROM_RESOURCE;
-    }
-
-    /**
      * Updates the local cache of the "avoid bad Wi-Fi" setting from the carrier config
      * for a specific subscription ID.
      * Must be called on the handler thread.
@@ -502,8 +508,8 @@
 
         // CarrierConfigManager#getConfigForSubId() is supported
         // only when system has FEATURE_TELEPHONY_SUBSCRIPTION
-        final boolean config = !mHasTelephonySubscription
-                || mDeps.readAvoidBadWifiFromCarrierConfig(mContext, subId);
+        final boolean config =
+                mDeps.readAvoidBadWifiFromCarrierConfig(mCarrierConfigManager, subId);
         mHandler.post(() -> updateAvoidBadWifiFromCarrierConfig(subId, config));
     }
 
@@ -528,33 +534,35 @@
      */
     public boolean updateAvoidBadWifi() {
         final boolean prevAvoid = mAvoidBadWifi;
-        if (getAvoidBadWifiSource() == AvoidBadWifiSource.FROM_CARRIER_CONFIG) {
-            // Force update activelyPreferBadWifi since it will always be true in Android U+,
-            // and mAvoidBadWifiFromCarrierConfigFeature is a trunk stable flag
-            // that only exists in 25Q4+
-            mActivelyPreferBadWifi = true;
-
-            final String settingAvoidBadWifiStr = readAvoidBadWifiFromSettings();
-            if (settingAvoidBadWifiStr != null) {
-                mAvoidBadWifi = "1".equals(settingAvoidBadWifiStr);
-            } else {
-                // Retrieve the avoid bad Wi-Fi setting from the local cache to avoid potential
-                // issues or blocking from the IPC call getAvoidBadWifiCarrierConfigForSubId().
-                mAvoidBadWifi = readAvoidBadWifiFromCache(mActiveSubId);
-            }
-            return mAvoidBadWifi != prevAvoid;
-        } else {
-            final boolean settingAvoidBadWifi = "1".equals(readAvoidBadWifiFromSettings());
-            mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
-            final boolean prevActive = mActivelyPreferBadWifi;
-            final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
-            if (null == deviceConfigPreferBadWifi) {
-                mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
-            } else {
-                mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
-            }
-
-            return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
+        switch (mAvoidBadWifiSource) {
+            case FROM_CARRIER_CONFIG:
+                // Force update activelyPreferBadWifi since it will always be true in Android U+,
+                // and mAvoidBadWifiFromCarrierConfigFeature is a trunk stable flag
+                // that only exists in 25Q4+
+                mActivelyPreferBadWifi = true;
+                final String settingAvoidBadWifiStr = readAvoidBadWifiFromSettings();
+                if (settingAvoidBadWifiStr != null) {
+                    mAvoidBadWifi = "1".equals(settingAvoidBadWifiStr);
+                } else {
+                    // Retrieve the avoid bad Wi-Fi setting from the local cache to avoid potential
+                    // issues or blocking from the IPC call getAvoidBadWifiCarrierConfigForSubId().
+                    mAvoidBadWifi = readAvoidBadWifiFromCache(mActiveSubId);
+                }
+                return mAvoidBadWifi != prevAvoid;
+            case FROM_RESOURCE:
+                final boolean settingAvoidBadWifi = "1".equals(readAvoidBadWifiFromSettings());
+                mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
+                final boolean prevActive = mActivelyPreferBadWifi;
+                final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
+                if (null == deviceConfigPreferBadWifi) {
+                    mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+                } else {
+                    mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
+                }
+                return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
+            default:
+                Log.wtf(TAG, "Unexpected avoid bad Wi-Fi source: " + mAvoidBadWifiSource);
+                return false;
         }
     }
 
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index a999306..1c642ae 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -87,7 +87,7 @@
 
     public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
     public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
-    public static final int SOCKET_DUMP_RECV_BUFSIZE = 128 * 1024;
+    public static final int SOCKET_DUMP_RECV_BUFSIZE = 1024 * 1024;
 
     /**
      * Return whether the input ByteBuffer contains enough remaining bytes for
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
index 95a6d8c..ca0a136 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
@@ -49,16 +49,19 @@
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeNotNull
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.any
 import org.mockito.Mockito.doCallRealMethod
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
@@ -74,6 +77,17 @@
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class MultinetworkPolicyTrackerTest {
+    companion object {
+        @Parameterized.Parameters
+        @JvmStatic
+        fun data(): Iterable<Any?> {
+            return mutableListOf(
+                null,
+                mock(CarrierConfigManager::class.java)
+            )
+        }
+    }
+
     // This wrapper class prevents JUnit from attempting to load unsupported system classes
     // that are present in the System Test (S/T) image, which would otherwise cause test failures.
     private class CarrierConfigChangeRunner(
@@ -97,6 +111,11 @@
 
     private val featureFlags = HashSet<String>()
 
+    // Indicates where carrierConfigManager is supported.
+    @Parameterized.Parameter(0)
+    @JvmField
+    var carrierConfigManager: CarrierConfigManager? = null
+
     // This will set feature flags from @FeatureFlag annotations
     // into the map before setUp() runs.
     @get:Rule
@@ -115,7 +134,6 @@
     }
 
     private val telephonyManager = mock(TelephonyManager::class.java)
-    private val carrierConfigManager = mock(CarrierConfigManager::class.java)
     private val subscriptionManager = mock(SubscriptionManager::class.java).also {
         doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt())
     }
@@ -135,8 +153,6 @@
         }
         doReturn(subscriptionManager).`when`(it)
             .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
-        doReturn(carrierConfigManager).`when`(it)
-            .getSystemService(CarrierConfigManager::class.java)
         doReturn(resolver).`when`(it).contentResolver
         doReturn(resources).`when`(it).resources
         doReturn(it).`when`(it).createConfigurationContext(any())
@@ -206,6 +222,8 @@
             featureFlags.contains(FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG)
         )
 
+        doReturn(carrierConfigManager).`when`(context)
+            .getSystemService(CarrierConfigManager::class.java)
         trackerDependencies.setBackgroundThreadHandler(bgHandler)
         tracker = MultinetworkPolicyTracker(
             context,
@@ -219,6 +237,9 @@
     @After
     fun tearDown() {
         ConnectivityResources.setResourcesContextForTest(null)
+        carrierConfigManager?.let { ccm ->
+            reset(ccm)
+        }
         trackerDependencies.resetAvoidBadWifiCarrierConfigForSubIdMap()
     }
 
@@ -326,6 +347,8 @@
     @FeatureFlag(name = FLAG_AVOID_BAD_WIFI_FROM_CARRIER_CONFIG, true)
     @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.BAKLAVA)
     fun testUpdateAvoidBadWifiOnCarrierConfigChange() {
+        assumeNotNull("skip test if carrierConfigManager is not supported", carrierConfigManager)
+
         // Mock the initial global setting to null
         Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, null)
 
@@ -346,7 +369,7 @@
         // Mock the initial carrier configuration to return false
         trackerDependencies.setAvoidBadWifiCarrierConfigForSubId(activeSubId, false)
         verify(carrierConfigManager, times(1))
-            .registerCarrierConfigChangeListener(any(), carrierConfiglistenCaptor.capture())
+            ?.registerCarrierConfigChangeListener(any(), carrierConfiglistenCaptor.capture())
 
         val carrierConfiglistener = carrierConfiglistenCaptor.value
         // dispatch for the first carrier config initialization on the handler thread
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
index 7285cbe..dc2ace4 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
@@ -1,11 +1,11 @@
 package com.android.server.connectivity
 
-import android.content.Context
 import android.content.res.Resources
 import android.os.Handler
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
 import android.provider.DeviceConfig.OnPropertiesChangedListener
+import android.telephony.CarrierConfigManager
 import com.android.internal.annotations.GuardedBy
 import com.android.internal.os.BackgroundThread
 import com.android.server.connectivity.MultinetworkPolicyTracker.CONFIG_ACTIVELY_PREFER_BAD_WIFI
@@ -72,7 +72,7 @@
         resources
 
     override fun readAvoidBadWifiFromCarrierConfig(
-        context: Context,
+        ccm: CarrierConfigManager?,
         subId: Int
     ): Boolean =
         avoidBadWifiCarrierConfigForSubIdMap.getOrDefault(subId, true)