Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a0ccc57 authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Automerger Merge Worker
Browse files

Merge "Remove usage of networkAttributes" am: f6ecb001

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1623260

Change-Id: I52a3125843e093c5a7d0d80c2a5c9608b397ea00
parents e28a4565 f6ecb001
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -42,4 +42,14 @@
        -->
    </string-array>

    <string-array translatable="false" name="config_legacy_networktype_restore_timers">
        <item>2,60000</item><!-- mobile_mms -->
        <item>3,60000</item><!-- mobile_supl -->
        <item>4,60000</item><!-- mobile_dun -->
        <item>5,60000</item><!-- mobile_hipri -->
        <item>10,60000</item><!-- mobile_fota -->
        <item>11,60000</item><!-- mobile_ims -->
        <item>12,60000</item><!-- mobile_cbs -->
    </string-array>

</resources>
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -17,11 +17,11 @@
    <overlayable name="ServiceConnectivityResourcesConfig">
        <policy type="product|system|vendor">
            <!-- Configuration values for ConnectivityService -->
            <item type="array" name="config_legacy_networktype_restore_timers"/>
            <item type="string" name="config_networkCaptivePortalServerUrl"/>
            <item type="integer" name="config_networkTransitionTimeout"/>
            <item type="array" name="config_wakeonlan_supported_interfaces"/>


        </policy>
    </overlayable>
</resources>
+107 −68
Original line number Diff line number Diff line
@@ -17,6 +17,10 @@
package com.android.server;

import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
@@ -28,9 +32,23 @@ import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -112,7 +130,6 @@ import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMonitorManager;
@@ -626,11 +643,8 @@ public class ConnectivityService extends IConnectivityManager.Stub

    private UserManager mUserManager;

    private NetworkConfig[] mNetConfigs;
    private int mNetworksDefined;

    // the set of network types that can only be enabled by system/sig apps
    private List mProtectedNetworks;
    private List<Integer> mProtectedNetworks;

    private Set<String> mWolSupportedInterfaces;

@@ -720,18 +734,63 @@ public class ConnectivityService extends IConnectivityManager.Stub
         *    They are therefore not thread-safe with respect to each other.
         *  - getNetworkForType() can be called at any time on binder threads. It is synchronized
         *    on mTypeLists to be thread-safe with respect to a concurrent remove call.
         *  - getRestoreTimerForType(type) is also synchronized on mTypeLists.
         *  - dump is thread-safe with respect to concurrent add and remove calls.
         */
        private final ArrayList<NetworkAgentInfo> mTypeLists[];
        @NonNull
        private final ConnectivityService mService;

        // Restore timers for requestNetworkForFeature (network type -> timer in ms). Types without
        // an entry have no timer (equivalent to -1). Lazily loaded.
        @NonNull
        private ArrayMap<Integer, Integer> mRestoreTimers = new ArrayMap<>();

        LegacyTypeTracker(@NonNull ConnectivityService service) {
            mService = service;
            mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
        }

        public void addSupportedType(int type) {
        public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) {
            final PackageManager pm = ctx.getPackageManager();
            if (pm.hasSystemFeature(FEATURE_WIFI)) {
                addSupportedType(TYPE_WIFI);
            }
            if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) {
                addSupportedType(TYPE_WIFI_P2P);
            }
            if (tm.isDataCapable()) {
                // Telephony does not have granular support for these types: they are either all
                // supported, or none is supported
                addSupportedType(TYPE_MOBILE);
                addSupportedType(TYPE_MOBILE_MMS);
                addSupportedType(TYPE_MOBILE_SUPL);
                addSupportedType(TYPE_MOBILE_DUN);
                addSupportedType(TYPE_MOBILE_HIPRI);
                addSupportedType(TYPE_MOBILE_FOTA);
                addSupportedType(TYPE_MOBILE_IMS);
                addSupportedType(TYPE_MOBILE_CBS);
                addSupportedType(TYPE_MOBILE_IA);
                addSupportedType(TYPE_MOBILE_EMERGENCY);
            }
            if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) {
                addSupportedType(TYPE_BLUETOOTH);
            }
            if (pm.hasSystemFeature(FEATURE_WATCH)) {
                // TYPE_PROXY is only used on Wear
                addSupportedType(TYPE_PROXY);
            }
            // Ethernet is often not specified in the configs, although many devices can use it via
            // USB host adapters. Add it as long as the ethernet service is here.
            if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) {
                addSupportedType(TYPE_ETHERNET);
            }

            // Always add TYPE_VPN as a supported type
            addSupportedType(TYPE_VPN);
        }

        private void addSupportedType(int type) {
            if (mTypeLists[type] != null) {
                throw new IllegalStateException(
                        "legacy list for type " + type + "already initialized");
@@ -752,6 +811,35 @@ public class ConnectivityService extends IConnectivityManager.Stub
            return null;
        }

        public int getRestoreTimerForType(int type) {
            synchronized (mTypeLists) {
                if (mRestoreTimers == null) {
                    mRestoreTimers = loadRestoreTimers();
                }
                return mRestoreTimers.getOrDefault(type, -1);
            }
        }

        private ArrayMap<Integer, Integer> loadRestoreTimers() {
            final String[] configs = mService.mResources.get().getStringArray(
                    com.android.connectivity.resources.R.array
                            .config_legacy_networktype_restore_timers);
            final ArrayMap<Integer, Integer> ret = new ArrayMap<>(configs.length);
            for (final String config : configs) {
                final String[] splits = TextUtils.split(config, ",");
                if (splits.length != 2) {
                    logwtf("Invalid restore timer token count: " + config);
                    continue;
                }
                try {
                    ret.put(Integer.parseInt(splits[0]), Integer.parseInt(splits[1]));
                } catch (NumberFormatException e) {
                    logwtf("Invalid restore timer number format: " + config, e);
                }
            }
            return ret;
        }

        private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
                boolean isDefaultNetwork) {
            if (DBG) {
@@ -1174,64 +1262,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
        mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);

        mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];

        // TODO: What is the "correct" way to do determine if this is a wifi only device?
        boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false);
        log("wifiOnly=" + wifiOnly);
        String[] naStrings = context.getResources().getStringArray(
                com.android.internal.R.array.networkAttributes);
        for (String naString : naStrings) {
            try {
                NetworkConfig n = new NetworkConfig(naString);
                if (VDBG) log("naString=" + naString + " config=" + n);
                if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
                    loge("Error in networkAttributes - ignoring attempt to define type " +
                            n.type);
                    continue;
                }
                if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
                    log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
                            n.type);
                    continue;
                }
                if (mNetConfigs[n.type] != null) {
                    loge("Error in networkAttributes - ignoring attempt to redefine type " +
                            n.type);
                    continue;
                }
                mLegacyTypeTracker.addSupportedType(n.type);

                mNetConfigs[n.type] = n;
                mNetworksDefined++;
            } catch(Exception e) {
                // ignore it - leave the entry null
            }
        }

        // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config.
        if (mNetConfigs[TYPE_VPN] == null) {
            // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we
            // don't need to add TYPE_VPN to mNetConfigs.
            mLegacyTypeTracker.addSupportedType(TYPE_VPN);
            mNetworksDefined++;  // used only in the log() statement below.
        }

        // Do the same for Ethernet, since it's often not specified in the configs, although many
        // devices can use it via USB host adapters.
        if (mNetConfigs[TYPE_ETHERNET] == null
                && mContext.getSystemService(Context.ETHERNET_SERVICE) != null) {
            mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
            mNetworksDefined++;
        }

        if (VDBG) log("mNetworksDefined=" + mNetworksDefined);

        mProtectedNetworks = new ArrayList<Integer>();
        mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager);
        mProtectedNetworks = new ArrayList<>();
        int[] protectedNetworks = context.getResources().getIntArray(
                com.android.internal.R.array.config_protectedNetworks);
        for (int p : protectedNetworks) {
            if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
            if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) {
                mProtectedNetworks.add(p);
            } else {
                if (DBG) loge("Ignoring protectedNetwork " + p);
@@ -2640,9 +2676,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
        // if the system property isn't set, use the value for the apn type
        int ret = RESTORE_DEFAULT_NETWORK_DELAY;

        if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
                (mNetConfigs[networkType] != null)) {
            ret = mNetConfigs[networkType].restoreTime;
        if (mLegacyTypeTracker.isTypeSupported(networkType)) {
            ret = mLegacyTypeTracker.getRestoreTimerForType(networkType);
        }
        return ret;
    }
@@ -4855,6 +4890,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
        Log.wtf(TAG, s);
    }

    private static void logwtf(String s, Throwable t) {
        Log.wtf(TAG, s, t);
    }

    private static void loge(String s) {
        Log.e(TAG, s);
    }
@@ -8917,13 +8956,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
        private int transportTypeToLegacyType(int type) {
            switch (type) {
                case NetworkCapabilities.TRANSPORT_CELLULAR:
                    return ConnectivityManager.TYPE_MOBILE;
                    return TYPE_MOBILE;
                case NetworkCapabilities.TRANSPORT_WIFI:
                    return ConnectivityManager.TYPE_WIFI;
                    return TYPE_WIFI;
                case NetworkCapabilities.TRANSPORT_BLUETOOTH:
                    return ConnectivityManager.TYPE_BLUETOOTH;
                    return TYPE_BLUETOOTH;
                case NetworkCapabilities.TRANSPORT_ETHERNET:
                    return ConnectivityManager.TYPE_ETHERNET;
                    return TYPE_ETHERNET;
                default:
                    loge("Unexpected transport in transportTypeToLegacyType: " + type);
            }
+8 −1
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -42,6 +44,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
@@ -1505,6 +1508,9 @@ public class ConnectivityServiceTest {
            Looper.prepare();
        }
        mockDefaultPackages();
        mockHasSystemFeature(FEATURE_WIFI, true);
        mockHasSystemFeature(FEATURE_WIFI_DIRECT, true);
        doReturn(true).when(mTelephonyManager).isDataCapable();
        FakeSettingsProvider.clearSettingsProvider();
        mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
@@ -1829,7 +1835,8 @@ public class ConnectivityServiceTest {
        assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
        assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
        assertFalse(mCm.isNetworkSupported(TYPE_PROXY));
        // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
        // mocks, this assert exercises the ConnectivityService code path that ensures that
+92 −35
Original line number Diff line number Diff line
@@ -21,13 +21,29 @@

package com.android.server

import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FEATURE_WIFI
import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
import android.net.ConnectivityManager.TYPE_ETHERNET
import android.net.ConnectivityManager.TYPE_MOBILE
import android.net.ConnectivityManager.TYPE_MOBILE_CBS
import android.net.ConnectivityManager.TYPE_MOBILE_DUN
import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY
import android.net.ConnectivityManager.TYPE_MOBILE_FOTA
import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI
import android.net.ConnectivityManager.TYPE_MOBILE_IA
import android.net.ConnectivityManager.TYPE_MOBILE_IMS
import android.net.ConnectivityManager.TYPE_MOBILE_MMS
import android.net.ConnectivityManager.TYPE_MOBILE_SUPL
import android.net.ConnectivityManager.TYPE_VPN
import android.net.ConnectivityManager.TYPE_WIFI
import android.net.ConnectivityManager.TYPE_WIFI_P2P
import android.net.ConnectivityManager.TYPE_WIMAX
import android.net.EthernetManager
import android.net.NetworkInfo.DetailedState.CONNECTED
import android.net.NetworkInfo.DetailedState.DISCONNECTED
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.server.ConnectivityService.LegacyTypeTracker
@@ -36,7 +52,6 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -52,88 +67,130 @@ const val UNSUPPORTED_TYPE = TYPE_WIMAX
@RunWith(AndroidJUnit4::class)
@SmallTest
class LegacyTypeTrackerTest {
    private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL)
    private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE,
            TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI,
            TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA,
            TYPE_MOBILE_EMERGENCY, TYPE_VPN)

    private val mMockService = mock(ConnectivityService::class.java).apply {
        doReturn(false).`when`(this).isDefaultNetwork(any())
    }
    private val mTracker = LegacyTypeTracker(mMockService).apply {
        supportedTypes.forEach {
            addSupportedType(it)
    private val mPm = mock(PackageManager::class.java)
    private val mContext = mock(Context::class.java).apply {
        doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI)
        doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
        doReturn(mPm).`when`(this).packageManager
        doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService(
                Context.ETHERNET_SERVICE)
    }
    private val mTm = mock(TelephonyManager::class.java).apply {
        doReturn(true).`when`(this).isDataCapable
    }

    private fun makeTracker() = LegacyTypeTracker(mMockService).apply {
        loadSupportedTypes(mContext, mTm)
    }

    @Test
    fun testSupportedTypes() {
        try {
            mTracker.addSupportedType(supportedTypes[0])
            fail("Expected IllegalStateException")
        } catch (expected: IllegalStateException) {}
        val tracker = makeTracker()
        supportedTypes.forEach {
            assertTrue(mTracker.isTypeSupported(it))
            assertTrue(tracker.isTypeSupported(it))
        }
        assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE))
    }

    @Test
    fun testSupportedTypes_NoEthernet() {
        doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE)
        assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET))
    }

    @Test
    fun testSupportedTypes_NoTelephony() {
        doReturn(false).`when`(mTm).isDataCapable
        val tracker = makeTracker()
        val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN)
        nonMobileTypes.forEach {
            assertTrue(tracker.isTypeSupported(it))
        }
        supportedTypes.toSet().minus(nonMobileTypes).forEach {
            assertFalse(tracker.isTypeSupported(it))
        }
    }

    @Test
    fun testSupportedTypes_NoWifiDirect() {
        doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
        val tracker = makeTracker()
        assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P))
        supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach {
            assertTrue(tracker.isTypeSupported(it))
        }
        assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE))
    }

    @Test
    fun testSupl() {
        val tracker = makeTracker()
        val mobileNai = mock(NetworkAgentInfo::class.java)
        mTracker.add(TYPE_MOBILE, mobileNai)
        tracker.add(TYPE_MOBILE, mobileNai)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE)
        reset(mMockService)
        mTracker.add(TYPE_MOBILE_SUPL, mobileNai)
        tracker.add(TYPE_MOBILE_SUPL, mobileNai)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
        reset(mMockService)
        mTracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
        tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
        reset(mMockService)
        mTracker.add(TYPE_MOBILE_SUPL, mobileNai)
        tracker.add(TYPE_MOBILE_SUPL, mobileNai)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
        reset(mMockService)
        mTracker.remove(mobileNai, false)
        tracker.remove(mobileNai, false)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE)
    }

    @Test
    fun testAddNetwork() {
        val tracker = makeTracker()
        val mobileNai = mock(NetworkAgentInfo::class.java)
        val wifiNai = mock(NetworkAgentInfo::class.java)
        mTracker.add(TYPE_MOBILE, mobileNai)
        mTracker.add(TYPE_WIFI, wifiNai)
        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
        tracker.add(TYPE_MOBILE, mobileNai)
        tracker.add(TYPE_WIFI, wifiNai)
        assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
        // Make sure adding a second NAI does not change the results.
        val secondMobileNai = mock(NetworkAgentInfo::class.java)
        mTracker.add(TYPE_MOBILE, secondMobileNai)
        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
        tracker.add(TYPE_MOBILE, secondMobileNai)
        assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
        // Make sure removing a network that wasn't added for this type is a no-op.
        mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
        tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
        assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
        // Remove the top network for mobile and make sure the second one becomes the network
        // of record for this type.
        mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
        assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
        assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
        tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
        assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
        assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
        // Make sure adding a network for an unsupported type does not register it.
        mTracker.add(UNSUPPORTED_TYPE, mobileNai)
        assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE))
        tracker.add(UNSUPPORTED_TYPE, mobileNai)
        assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE))
    }

    @Test
    fun testBroadcastOnDisconnect() {
        val tracker = makeTracker()
        val mobileNai1 = mock(NetworkAgentInfo::class.java)
        val mobileNai2 = mock(NetworkAgentInfo::class.java)
        doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1)
        mTracker.add(TYPE_MOBILE, mobileNai1)
        tracker.add(TYPE_MOBILE, mobileNai1)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE)
        reset(mMockService)
        doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2)
        mTracker.add(TYPE_MOBILE, mobileNai2)
        tracker.add(TYPE_MOBILE, mobileNai2)
        verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt())
        mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
        tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE)
        verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE)
    }