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

Commit f6ecb001 authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN Committed by Gerrit Code Review
Browse files

Merge "Remove usage of networkAttributes"

parents b67cbe26 62966b71
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)
    }