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

Commit fb878b66 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Isolate NetworkStateTracker creation, test.

Change ConnectivityService to use a factory when creating
NetworkStateTrackers, which gives us a good place to inject mocks
for testing.  Add initial tests to verify that network routes are
added and removed as networks changed.

Change-Id: I11cbc61a84c2ed4afa2670036295b1494eab26e1
parent 088f29f5
Loading
Loading
Loading
Loading
+102 −70
Original line number Diff line number Diff line
@@ -20,6 +20,13 @@ import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_DUMMY;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -86,14 +93,13 @@ import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;

import dalvik.system.DexClassLoader;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -317,6 +323,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {

    public ConnectivityService(Context context, INetworkManagementService netd,
            INetworkStatsService statsService, INetworkPolicyManager policyManager) {
        // Currently, omitting a NetworkFactory will create one internally
        // TODO: create here when we have cleaner WiMAX support
        this(context, netd, statsService, policyManager, null);
    }

    public ConnectivityService(Context context, INetworkManagementService netd,
            INetworkStatsService statsService, INetworkPolicyManager policyManager,
            NetworkFactory netFactory) {
        if (DBG) log("ConnectivityService starting up");

        HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
@@ -324,6 +338,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        mHandler = new InternalHandler(handlerThread.getLooper());
        mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper());

        if (netFactory == null) {
            netFactory = new DefaultNetworkFactory(context, mTrackerHandler);
        }

        // setup our unique device name
        if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
            String id = Settings.Secure.getString(context.getContentResolver(),
@@ -462,59 +480,27 @@ public class ConnectivityService extends IConnectivityManager.Stub {

        mTestMode = SystemProperties.get("cm.test.mode").equals("true")
                && SystemProperties.get("ro.build.type").equals("eng");
        /*
         * Create the network state trackers for Wi-Fi and mobile
         * data. Maybe this could be done with a factory class,
         * but it's not clear that it's worth it, given that
         * the number of different network types is not going
         * to change very often.
         */
        for (int netType : mPriorityList) {
            switch (mNetConfigs[netType].radio) {
            case ConnectivityManager.TYPE_WIFI:
                mNetTrackers[netType] = new WifiStateTracker(
                        netType, mNetConfigs[netType].name);
                mNetTrackers[netType].startMonitoring(context, mTrackerHandler);
                break;
            case ConnectivityManager.TYPE_MOBILE:
                mNetTrackers[netType] = new MobileDataStateTracker(netType,
                        mNetConfigs[netType].name);
                mNetTrackers[netType].startMonitoring(context, mTrackerHandler);
                break;
            case ConnectivityManager.TYPE_DUMMY:
                mNetTrackers[netType] = new DummyDataStateTracker(netType,
                        mNetConfigs[netType].name);
                mNetTrackers[netType].startMonitoring(context, mTrackerHandler);
                break;
            case ConnectivityManager.TYPE_BLUETOOTH:
                mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance();
                mNetTrackers[netType].startMonitoring(context, mTrackerHandler);
                break;
            case ConnectivityManager.TYPE_WIMAX:
                mNetTrackers[netType] = makeWimaxStateTracker();
                if (mNetTrackers[netType]!= null) {
                    mNetTrackers[netType].startMonitoring(context, mTrackerHandler);
                }
                break;
            case ConnectivityManager.TYPE_ETHERNET:
                mNetTrackers[netType] = EthernetDataTracker.getInstance();
                mNetTrackers[netType].startMonitoring(context, mTrackerHandler);
                break;
            default:
                loge("Trying to create a DataStateTracker for an unknown radio type " +
                        mNetConfigs[netType].radio);

        // Create and start trackers for hard-coded networks
        for (int targetNetworkType : mPriorityList) {
            final NetworkConfig config = mNetConfigs[targetNetworkType];
            final NetworkStateTracker tracker;
            try {
                tracker = netFactory.createTracker(targetNetworkType, config);
                mNetTrackers[targetNetworkType] = tracker;
            } catch (IllegalArgumentException e) {
                Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType)
                        + " tracker: " + e);
                continue;
            }
            mCurrentLinkProperties[netType] = null;
            if (mNetTrackers[netType] != null && mNetConfigs[netType].isDefault()) {
                mNetTrackers[netType].reconnect();

            tracker.startMonitoring(context, mTrackerHandler);
            if (config.isDefault()) {
                tracker.reconnect();
            }
        }

        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);

        mTethering = new Tethering(mContext, nmService, statsService, this, mHandler.getLooper());
        mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
        mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
                                  mTethering.getTetherableWifiRegexs().length != 0 ||
                                  mTethering.getTetherableBluetoothRegexs().length != 0) &&
@@ -523,9 +509,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        mVpn = new Vpn(mContext, new VpnCallback());

        try {
            nmService.registerObserver(mTethering);
            nmService.registerObserver(mVpn);
            nmService.registerObserver(mDataActivityObserver);
            mNetd.registerObserver(mTethering);
            mNetd.registerObserver(mVpn);
            mNetd.registerObserver(mDataActivityObserver);
        } catch (RemoteException e) {
            loge("Error registering observer :" + e);
        }
@@ -540,7 +526,53 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        loadGlobalProxy();
    }

    private NetworkStateTracker makeWimaxStateTracker() {
    /**
     * Factory that creates {@link NetworkStateTracker} instances using given
     * {@link NetworkConfig}.
     */
    public interface NetworkFactory {
        public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config);
    }

    private static class DefaultNetworkFactory implements NetworkFactory {
        private final Context mContext;
        private final Handler mTrackerHandler;

        public DefaultNetworkFactory(Context context, Handler trackerHandler) {
            mContext = context;
            mTrackerHandler = trackerHandler;
        }

        @Override
        public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) {
            switch (config.radio) {
                case TYPE_WIFI:
                    return new WifiStateTracker(targetNetworkType, config.name);
                case TYPE_MOBILE:
                    return new MobileDataStateTracker(targetNetworkType, config.name);
                case TYPE_DUMMY:
                    return new DummyDataStateTracker(targetNetworkType, config.name);
                case TYPE_BLUETOOTH:
                    return BluetoothTetheringDataTracker.getInstance();
                case TYPE_WIMAX:
                    return makeWimaxStateTracker(mContext, mTrackerHandler);
                case TYPE_ETHERNET:
                    return EthernetDataTracker.getInstance();
                default:
                    throw new IllegalArgumentException(
                            "Trying to create a NetworkStateTracker for an unknown radio type: "
                            + config.radio);
            }
        }
    }

    /**
     * Loads external WiMAX library and registers as system service, returning a
     * {@link NetworkStateTracker} for WiMAX. Caller is still responsible for
     * invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}.
     */
    private static NetworkStateTracker makeWimaxStateTracker(
            Context context, Handler trackerHandler) {
        // Initialize Wimax
        DexClassLoader wimaxClassLoader;
        Class wimaxStateTrackerClass = null;
@@ -554,25 +586,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {

        NetworkStateTracker wimaxStateTracker = null;

        boolean isWimaxEnabled = mContext.getResources().getBoolean(
        boolean isWimaxEnabled = context.getResources().getBoolean(
                com.android.internal.R.bool.config_wimaxEnabled);

        if (isWimaxEnabled) {
            try {
                wimaxJarLocation = mContext.getResources().getString(
                wimaxJarLocation = context.getResources().getString(
                        com.android.internal.R.string.config_wimaxServiceJarLocation);
                wimaxLibLocation = mContext.getResources().getString(
                wimaxLibLocation = context.getResources().getString(
                        com.android.internal.R.string.config_wimaxNativeLibLocation);
                wimaxManagerClassName = mContext.getResources().getString(
                wimaxManagerClassName = context.getResources().getString(
                        com.android.internal.R.string.config_wimaxManagerClassname);
                wimaxServiceClassName = mContext.getResources().getString(
                wimaxServiceClassName = context.getResources().getString(
                        com.android.internal.R.string.config_wimaxServiceClassname);
                wimaxStateTrackerClassName = mContext.getResources().getString(
                wimaxStateTrackerClassName = context.getResources().getString(
                        com.android.internal.R.string.config_wimaxStateTrackerClassname);

                log("wimaxJarLocation: " + wimaxJarLocation);
                wimaxClassLoader =  new DexClassLoader(wimaxJarLocation,
                        new ContextWrapper(mContext).getCacheDir().getAbsolutePath(),
                        new ContextWrapper(context).getCacheDir().getAbsolutePath(),
                        wimaxLibLocation, ClassLoader.getSystemClassLoader());

                try {
@@ -593,13 +625,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {

                Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor
                        (new Class[] {Context.class, Handler.class});
                wimaxStateTracker = (NetworkStateTracker)wmxStTrkrConst.newInstance(mContext,
                        mTrackerHandler);
                wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance(
                        context, trackerHandler);

                Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor
                        (new Class[] {Context.class, wimaxStateTrackerClass});
                wmxSrvConst.setAccessible(true);
                IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(mContext, wimaxStateTracker);
                IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker);
                wmxSrvConst.setAccessible(false);

                ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker);
@@ -1873,6 +1905,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        // snapshot isFailover, because sendConnectedBroadcast() resets it
        boolean isFailover = info.isFailover();
        final NetworkStateTracker thisNet = mNetTrackers[type];
        final String thisIface = thisNet.getLinkProperties().getInterfaceName();

        // if this is a default net and other default is running
        // kill the one not preferred
@@ -1931,10 +1964,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());

        // notify battery stats service about this network
        final String iface = thisNet.getLinkProperties().getInterfaceName();
        if (iface != null) {
        if (thisIface != null) {
            try {
                BatteryStatsService.getService().noteNetworkInterfaceType(iface, type);
                BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, type);
            } catch (RemoteException e) {
                // ignored; service lives in system_server
            }
@@ -2924,11 +2956,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
        }
    }

    private void log(String s) {
    private static void log(String s) {
        Slog.d(TAG, s);
    }

    private void loge(String s) {
    private static void loge(String s) {
        Slog.e(TAG, s);
    }

+2 −1
Original line number Diff line number Diff line
@@ -9,7 +9,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_STATIC_JAVA_LIBRARIES := \
    easymocklib \
    guava
    guava \
    littlemock

LOCAL_JAVA_LIBRARIES := android.test.runner services

+224 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.NetworkStateTracker.EVENT_STATE_CHANGED;
import static com.google.testing.littlemock.LittleMock.anyInt;
import static com.google.testing.littlemock.LittleMock.createCaptor;
import static com.google.testing.littlemock.LittleMock.doNothing;
import static com.google.testing.littlemock.LittleMock.doReturn;
import static com.google.testing.littlemock.LittleMock.doThrow;
import static com.google.testing.littlemock.LittleMock.eq;
import static com.google.testing.littlemock.LittleMock.isA;
import static com.google.testing.littlemock.LittleMock.mock;
import static com.google.testing.littlemock.LittleMock.reset;
import static com.google.testing.littlemock.LittleMock.verify;

import android.content.Context;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
import android.net.RouteInfo;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.util.LogPrinter;

import com.google.testing.littlemock.ArgumentCaptor;

import java.net.InetAddress;
import java.util.concurrent.Future;

/**
 * Tests for {@link ConnectivityService}.
 */
@LargeTest
public class ConnectivityServiceTest extends AndroidTestCase {
    private static final String TAG = "ConnectivityServiceTest";

    private static final String MOBILE_IFACE = "rmnet3";
    private static final String WIFI_IFACE = "wlan6";

    private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"));
    private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"));

    private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(
            parse("192.168.0.66"), parse("192.168.0.1"));
    private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(
            parse("fd00::66"), parse("fd00::"));

    private INetworkManagementService mNetManager;
    private INetworkStatsService mStatsService;
    private INetworkPolicyManager mPolicyService;
    private ConnectivityService.NetworkFactory mNetFactory;

    private BroadcastInterceptingContext mServiceContext;
    private ConnectivityService mService;

    private MockNetwork mMobile;
    private MockNetwork mWifi;

    private Handler mTrackerHandler;

    private static class MockNetwork {
        public NetworkStateTracker tracker;
        public NetworkInfo info;
        public LinkProperties link;

        public MockNetwork(int type) {
            tracker = mock(NetworkStateTracker.class);
            info = new NetworkInfo(type, -1, getNetworkTypeName(type), null);
            link = new LinkProperties();
        }

        public void doReturnDefaults() {
            // TODO: eventually CS should make defensive copies
            doReturn(new NetworkInfo(info)).when(tracker).getNetworkInfo();
            doReturn(new LinkProperties(link)).when(tracker).getLinkProperties();

            // fallback to default TCP buffers
            doReturn("").when(tracker).getTcpBufferSizesPropName();
        }
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();

        mServiceContext = new BroadcastInterceptingContext(getContext());

        mNetManager = mock(INetworkManagementService.class);
        mStatsService = mock(INetworkStatsService.class);
        mPolicyService = mock(INetworkPolicyManager.class);
        mNetFactory = mock(ConnectivityService.NetworkFactory.class);

        mMobile = new MockNetwork(TYPE_MOBILE);
        mWifi = new MockNetwork(TYPE_WIFI);

        // omit most network trackers
        doThrow(new IllegalArgumentException("Not supported in test environment"))
                .when(mNetFactory).createTracker(anyInt(), isA(NetworkConfig.class));

        doReturn(mMobile.tracker)
                .when(mNetFactory).createTracker(eq(TYPE_MOBILE), isA(NetworkConfig.class));
        doReturn(mWifi.tracker)
                .when(mNetFactory).createTracker(eq(TYPE_WIFI), isA(NetworkConfig.class));

        final ArgumentCaptor<Handler> trackerHandler = createCaptor();
        doNothing().when(mMobile.tracker)
                .startMonitoring(isA(Context.class), trackerHandler.capture());

        mService = new ConnectivityService(
                mServiceContext, mNetManager, mStatsService, mPolicyService, mNetFactory);
        mService.systemReady();

        mTrackerHandler = trackerHandler.getValue();
        mTrackerHandler.getLooper().setMessageLogging(new LogPrinter(Log.INFO, TAG));
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    public void testMobileConnectedAddedRoutes() throws Exception {
        Future<?> nextConnBroadcast;

        // bring up mobile network
        mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null);
        mMobile.link.setInterfaceName(MOBILE_IFACE);
        mMobile.link.addRoute(MOBILE_ROUTE_V4);
        mMobile.link.addRoute(MOBILE_ROUTE_V6);
        mMobile.doReturnDefaults();

        nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
        nextConnBroadcast.get();

        // verify that both routes were added and DNS was flushed
        verify(mNetManager).addRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V4));
        verify(mNetManager).addRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V6));
        verify(mNetManager).flushInterfaceDnsCache(MOBILE_IFACE);

    }

    public void testMobileWifiHandoff() throws Exception {
        Future<?> nextConnBroadcast;

        // bring up mobile network
        mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null);
        mMobile.link.setInterfaceName(MOBILE_IFACE);
        mMobile.link.addRoute(MOBILE_ROUTE_V4);
        mMobile.link.addRoute(MOBILE_ROUTE_V6);
        mMobile.doReturnDefaults();

        nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
        nextConnBroadcast.get();

        reset(mNetManager);

        // now bring up wifi network
        mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null);
        mWifi.link.setInterfaceName(WIFI_IFACE);
        mWifi.link.addRoute(WIFI_ROUTE_V4);
        mWifi.link.addRoute(WIFI_ROUTE_V6);
        mWifi.doReturnDefaults();

        // expect that mobile will be torn down
        doReturn(true).when(mMobile.tracker).teardown();

        nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget();
        nextConnBroadcast.get();

        // verify that wifi routes added, and teardown requested
        verify(mNetManager).addRoute(eq(WIFI_IFACE), eq(WIFI_ROUTE_V4));
        verify(mNetManager).addRoute(eq(WIFI_IFACE), eq(WIFI_ROUTE_V6));
        verify(mNetManager).flushInterfaceDnsCache(WIFI_IFACE);
        verify(mMobile.tracker).teardown();

        reset(mNetManager, mMobile.tracker);

        // tear down mobile network, as requested
        mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null);
        mMobile.link.clear();
        mMobile.doReturnDefaults();

        nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
        nextConnBroadcast.get();

        verify(mNetManager).removeRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V4));
        verify(mNetManager).removeRoute(eq(MOBILE_IFACE), eq(MOBILE_ROUTE_V6));

    }

    private static InetAddress parse(String addr) {
        return InetAddress.parseNumericAddress(addr);
    }
}